# HG changeset patch # User Sebastien Jodogne # Date 1403697425 -7200 # Node ID e57e08ed510f1cef452bb7e0675bf91b5de2394f # Parent c19552f604d50af5f4aa80245aa5ed94e5b1b37f# Parent b3f6fb1130cd60680e954fe055ce74214b18bf57 integration mainline -> dicom-rt diff -r b3f6fb1130cd -r e57e08ed510f CMakeLists.txt --- a/CMakeLists.txt Wed Jun 25 11:36:41 2014 +0200 +++ b/CMakeLists.txt Wed Jun 25 13:57:05 2014 +0200 @@ -300,6 +300,8 @@ STATIC ${DCMTK_SOURCES} ${ORTHANC_SERVER_SOURCES} + + OrthancServer/OrthancRestApi/RadiotherapyRestApi.cpp ) # Ensure autogenerated code is built before building ServerLibrary diff -r b3f6fb1130cd -r e57e08ed510f Core/Toolbox.cpp diff -r b3f6fb1130cd -r e57e08ed510f Core/Toolbox.h diff -r b3f6fb1130cd -r e57e08ed510f OrthancServer/FromDcmtkBridge.cpp diff -r b3f6fb1130cd -r e57e08ed510f OrthancServer/FromDcmtkBridge.h diff -r b3f6fb1130cd -r e57e08ed510f OrthancServer/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Jun 25 11:36:41 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Jun 25 13:57:05 2014 +0200 @@ -93,5 +93,7 @@ RegisterArchive(); Register("/instances", UploadDicomFile); + + RegisterRadiotherapy(); } } diff -r b3f6fb1130cd -r e57e08ed510f OrthancServer/OrthancRestApi/OrthancRestApi.h --- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Jun 25 11:36:41 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Jun 25 13:57:05 2014 +0200 @@ -44,7 +44,7 @@ public: typedef std::set SetOfStrings; - private: + protected: ServerContext& context_; void RegisterSystem(); @@ -59,6 +59,8 @@ void RegisterArchive(); + void RegisterRadiotherapy(); + public: OrthancRestApi(ServerContext& context); diff -r b3f6fb1130cd -r e57e08ed510f OrthancServer/OrthancRestApi/RadiotherapyRestApi.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/RadiotherapyRestApi.cpp Wed Jun 25 13:57:05 2014 +0200 @@ -0,0 +1,816 @@ +/** + * 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 . + **/ + + +#include "OrthancRestApi.h" + +#include "../ServerToolbox.h" + + + +// 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 +#include +#include +#include +#include + +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 points; + Toolbox::TokenizeString(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(points[k])); + p.append(boost::lexical_cast(points[k + 1])); + p.append(boost::lexical_cast(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(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 color; + Toolbox::TokenizeString(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(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) + { + std::string content; + context.ReadFile(content, instanceId, FileContentType_Dicom); + + ParsedDicomFile dicom(content); + + 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(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 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(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(info[SLICE_THICKNESS]["Value"].asString()); + return true; + } + } + + return false; + } + + + + static void GetRtStructuresInfo(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(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) + { + ServerContext& context = OrthancRestApi::GetContext(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(content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString())); + } + } + } + + call.GetOutput().AnswerJson(result); + } + } + + + static void GetRtStructuresROI(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(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) + { + ServerContext& context = OrthancRestApi::GetContext(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) + { + ServerContext& context = OrthancRestApi::GetContext(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) + { + ServerContext& context = OrthancRestApi::GetContext(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(call.GetUriComponent("polygon", "")); + + if (GetClosedPlanarPoints(result, context, instanceId, roi, index)) + { + call.GetOutput().AnswerJson(result); + } + } + } + + + static void GetRtStructuresClosedPlanarThickness(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(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(call.GetUriComponent("polygon", "")); + + float thickness; + if (GetRtStructuresClosedPlanarThickness(thickness, context, contour[index])) + { + call.GetOutput().AnswerBuffer(boost::lexical_cast(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::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 point_type; + + boost::geometry::model::linestring 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(x, y); + } + + boost::geometry::model::polygon poly; + boost::geometry::append(poly, points); + + area = abs(boost::geometry::area(poly)); + return true; + } + + + static void GetRtStructuresClosedPlanarArea(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(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(call.GetUriComponent("polygon", "")); + + double area; + if (GetClosedPlanarPoints(vertices, context, instanceId, roi, index) && + ComputeClosedPlanarArea(area, vertices)) + { + call.GetOutput().AnswerBuffer(boost::lexical_cast(area), "text/plain"); + } + } + } + + + static void GetRtStructuresInstanceOfClosedPlanar(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(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(call.GetUriComponent("polygon", "")); + + Json::Value result; + if (LookupReferencedInstance(result, context, contour[index])) + { + call.GetOutput().AnswerJson(result); + } + } + } + + + static void GetRtStructuresListOfInstances(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(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 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) + { + ServerContext& context = OrthancRestApi::GetContext(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) + { + ServerContext& context = OrthancRestApi::GetContext(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) + { + ServerContext& context = OrthancRestApi::GetContext(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(thickness); + } + } + + call.GetOutput().AnswerBuffer(boost::lexical_cast(volume), "text/plain"); + } + } + + + void OrthancRestApi::RegisterRadiotherapy() + { + 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); + } + +} + + +/** + storescu localhost 4242 ~/DICOM/Akos/data1/*.dcm + curl http://localhost:8042/series/10668f4a-fcc8fd1c-832e409c-5c7c018f-7ac8d3d9/rt-structures + curl http://localhost:8042/series/10668f4a-fcc8fd1c-832e409c-5c7c018f-7ac8d3d9/rt-structures/roi/5/closed-planar/19/vertices +**/ diff -r b3f6fb1130cd -r e57e08ed510f OrthancServer/ParsedDicomFile.cpp --- a/OrthancServer/ParsedDicomFile.cpp Wed Jun 25 11:36:41 2014 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Wed Jun 25 13:57:05 2014 +0200 @@ -859,14 +859,15 @@ } + - bool ParsedDicomFile::GetTagValue(std::string& value, - const DicomTag& tag) + static bool GetTagValueInternal(std::string& value, + DcmItem& item, + const DicomTag& tag) { DcmTagKey k(tag.GetGroup(), tag.GetElement()); - DcmDataset& dataset = *pimpl_->file_->getDataset(); DcmElement* element = NULL; - if (!dataset.findAndGetElement(k, element).good() || + if (!item.findAndGetElement(k, element).good() || element == NULL) { return false; @@ -883,7 +884,15 @@ value = v->AsString(); } - return true; + return true; + } + + + bool ParsedDicomFile::GetTagValue(std::string& value, + const DicomTag& tag) + { + DcmDataset& dataset = *pimpl_->file_->getDataset(); + return GetTagValueInternal(value, dataset, tag); } @@ -1279,4 +1288,45 @@ writer.WriteToMemory(result, accessor); } + + bool ParsedDicomFile::GetTagValue(std::string& value, + const SequencePath& path, + const DicomTag& tag) + { + if (path.size() == 0) + { + return GetTagValue(value, tag); + } + + DcmItem* current = pimpl_->file_->getDataset(); + assert(current != NULL); + + for (SequencePath::const_iterator it = path.begin(); it != path.end(); it++) + { + DcmTagKey k(it->first.GetGroup(), it->first.GetElement()); + + DcmSequenceOfItems* sequence = NULL; + if (!current->findAndGetSequence(k, sequence).good() || + sequence == NULL || + sequence->getVR() != EVR_SQ) + { + return false; + } + + if (it->second < 0 || it->second > sequence->card()) + { + return false; + } + + current = sequence->getItem(it->second); + + if (current == NULL) + { + return false; + } + } + + return GetTagValueInternal(value, *current, tag); + } + } diff -r b3f6fb1130cd -r e57e08ed510f OrthancServer/ParsedDicomFile.h --- a/OrthancServer/ParsedDicomFile.h Wed Jun 25 11:36:41 2014 +0200 +++ b/OrthancServer/ParsedDicomFile.h Wed Jun 25 13:57:05 2014 +0200 @@ -42,6 +42,9 @@ { class ParsedDicomFile : public IDynamicObject { + public: + typedef std::list< std::pair > SequencePath; + private: struct PImpl; PImpl* pimpl_; @@ -104,6 +107,11 @@ void ExtractPngImage(std::string& result, unsigned int frame, ImageExtractionMode mode); + + bool GetTagValue(std::string& value, + const SequencePath& path, + const DicomTag& tag); + }; } diff -r b3f6fb1130cd -r e57e08ed510f OrthancServer/ServerContext.h diff -r b3f6fb1130cd -r e57e08ed510f OrthancServer/main.cpp diff -r b3f6fb1130cd -r e57e08ed510f UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Wed Jun 25 11:36:41 2014 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Jun 25 13:57:05 2014 +0200 @@ -579,10 +579,44 @@ ASSERT_EQ("cd", t[1]); ASSERT_EQ("ef", t[2]); ASSERT_EQ("", t[3]); + + Toolbox::TokenizeString(t, "", '|'); + ASSERT_EQ(1, t.size()); + + Toolbox::TokenizeString(t, "aaaaa", '|'); + ASSERT_EQ(1, t.size()); + ASSERT_EQ("aaaaa", t[0]); + + Toolbox::TokenizeString(t, "aaa|aa", '|'); + ASSERT_EQ(2, t.size()); + ASSERT_EQ("aaa", t[0]); + ASSERT_EQ("aa", t[1]); + + Toolbox::TokenizeString(t, "a|aa|ab", '|'); + ASSERT_EQ(3, t.size()); + ASSERT_EQ("a", t[0]); + ASSERT_EQ("aa", t[1]); + ASSERT_EQ("ab", t[2]); + + Toolbox::TokenizeString(t, "||ab", '|'); + ASSERT_EQ(3, t.size()); + ASSERT_EQ("", t[0]); + ASSERT_EQ("", t[1]); + ASSERT_EQ("ab", t[2]); + + Toolbox::TokenizeString(t, "|", '|'); + ASSERT_EQ(2, t.size()); + ASSERT_EQ("", t[0]); + ASSERT_EQ("", t[1]); + + Toolbox::TokenizeString(t, "||", '|'); + ASSERT_EQ(3, t.size()); + ASSERT_EQ("", t[0]); + ASSERT_EQ("", t[1]); + ASSERT_EQ("", t[2]); } - #if defined(__linux) #include #endif