Mercurial > hg > orthanc
changeset 759:8cfc6119a5bd dicom-rt
integration mainline -> dicom-rt
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 16 Apr 2014 16:04:55 +0200 |
parents | b82292ba2083 (diff) 40d09221077a (current diff) |
children | 12a3f2eaa99a |
files | CMakeLists.txt Core/Toolbox.cpp Core/Toolbox.h OrthancCppClient/SharedLibrary/Laaw/laaw-exports.h OrthancCppClient/SharedLibrary/Laaw/laaw.h OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/OrthancRestApi.cpp OrthancServer/OrthancRestApi.h OrthancServer/OrthancRestApi/OrthancRestApi.cpp OrthancServer/OrthancRestApi/OrthancRestApi.h OrthancServer/ServerContext.h OrthancServer/main.cpp Resources/Archives/MessageWithDestination.cpp Resources/Archives/OrthancCppClient.cmake Resources/Archives/PrepareDatabase-v1.sql UnitTests/FileStorage.cpp UnitTests/Lua.cpp UnitTests/MemoryCache.cpp UnitTests/Png.cpp UnitTests/RestApi.cpp UnitTests/SQLite.cpp UnitTests/SQLiteChromium.cpp UnitTests/ServerIndex.cpp UnitTests/Versions.cpp UnitTests/Zip.cpp UnitTests/main.cpp UnitTestsSources/UnitTestsMain.cpp |
diffstat | 8 files changed, 969 insertions(+), 7 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Wed Apr 16 12:12:55 2014 +0200 +++ b/CMakeLists.txt Wed Apr 16 16:04:55 2014 +0200 @@ -222,6 +222,8 @@ OrthancServer/ServerToolbox.cpp OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancMoveRequestHandler.cpp + + #OrthancServer/RadiotherapyRestApi.cpp ) # Ensure autogenerated code is built before building ServerLibrary
--- a/OrthancServer/FromDcmtkBridge.cpp Wed Apr 16 12:12:55 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Apr 16 16:04:55 2014 +0200 @@ -839,13 +839,13 @@ - 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 = *file_->getDataset(); DcmElement* element = NULL; - if (!dataset.findAndGetElement(k, element).good() || + if (!item.findAndGetElement(k, element).good() || element == NULL) { return false; @@ -862,7 +862,57 @@ value = v->AsString(); } - return true; + return true; + } + + + bool ParsedDicomFile::GetTagValue(std::string& value, + const DicomTag& tag) + { + DcmDataset& dataset = *file_->getDataset(); + return GetTagValueInternal(value, dataset, tag); + } + + + + bool ParsedDicomFile::GetTagValue(std::string& value, + const SequencePath& path, + const DicomTag& tag) + { + if (path.size() == 0) + { + return GetTagValue(value, tag); + } + + DcmItem* current = 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); }
--- a/OrthancServer/FromDcmtkBridge.h Wed Apr 16 12:12:55 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Wed Apr 16 16:04:55 2014 +0200 @@ -72,6 +72,8 @@ size_t size); public: + typedef std::list< std::pair<DicomTag, unsigned int> > SequencePath; + ParsedDicomFile(const char* content, size_t size) { @@ -115,6 +117,10 @@ bool GetTagValue(std::string& value, const DicomTag& tag); + bool GetTagValue(std::string& value, + const SequencePath& path, + const DicomTag& tag); + DicomInstanceHasher GetHasher(); };
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Apr 16 12:12:55 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Apr 16 16:04:55 2014 +0200 @@ -44,7 +44,7 @@ public: typedef std::set<std::string> SetOfStrings; - private: + protected: ServerContext& context_; void RegisterSystem();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/RadiotherapyRestApi.cpp Wed Apr 16 16:04:55 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/RadiotherapyRestApi.h Wed Apr 16 16:04:55 2014 +0200 @@ -0,0 +1,44 @@ +/** + * 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); + }; +}
--- a/OrthancServer/ServerContext.h Wed Apr 16 12:12:55 2014 +0200 +++ b/OrthancServer/ServerContext.h Wed Apr 16 16:04:55 2014 +0200 @@ -40,6 +40,8 @@ #include "ServerIndex.h" #include "FromDcmtkBridge.h" +#include <boost/thread.hpp> + namespace Orthanc { /** @@ -63,6 +65,8 @@ virtual IDynamicObject* Provide(const std::string& id); }; + boost::mutex cacheMutex_; + FileStorage storage_; ServerIndex index_; CompressedFileStorageAccessor accessor_; @@ -139,6 +143,13 @@ // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD ParsedDicomFile& GetDicomFile(const std::string& instancePublicId); + boost::mutex& GetDicomFileMutex() + { + // TODO IMPROVE MULTITHREADING + // Every call to "ParsedDicomFile" must lock this mutex!!! + return cacheMutex_; + } + LuaContext& GetLuaContext() { return lua_;
--- a/UnitTestsSources/UnitTestsMain.cpp Wed Apr 16 12:12:55 2014 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Apr 16 16:04:55 2014 +0200 @@ -554,10 +554,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 <endian.h> #endif