Mercurial > hg > orthanc
changeset 532:b22312081388 dicom-rt
extract roi geometry
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 30 Aug 2013 16:09:19 +0200 |
parents | bd2087bb6450 |
children | da8e064d0d49 |
files | Core/Toolbox.cpp Core/Toolbox.h OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/RadiotherapyRestApi.cpp UnitTests/main.cpp |
diffstat | 6 files changed, 263 insertions(+), 14 deletions(-) [+] |
line wrap: on
line diff
--- a/Core/Toolbox.cpp Thu Aug 29 17:59:09 2013 +0200 +++ b/Core/Toolbox.cpp Fri Aug 30 16:09:19 2013 +0200 @@ -717,4 +717,47 @@ throw OrthancException(ErrorCode_NotImplemented); } } + + + + void Toolbox::Split(std::vector<std::string>& result, + const std::string& source, + char delimiter) + { + if (source.size() == 0) + { + result.clear(); + return; + } + + size_t count = 1; + for (size_t i = 0; i < source.size(); i++) + { + if (source[i] == delimiter) + { + count++; + } + } + + result.clear(); + result.resize(count); + + size_t pos = 0; + size_t start = 0; + while (start < source.size()) + { + assert(pos < count); + + size_t end = start; + while (end < source.size() && + source[end] != delimiter) + { + end++; + } + + result[pos++] = source.substr(start, end - start); + start = end + 1; + } + } + }
--- a/Core/Toolbox.h Thu Aug 29 17:59:09 2013 +0200 +++ b/Core/Toolbox.h Fri Aug 30 16:09:19 2013 +0200 @@ -108,5 +108,9 @@ void UrlDecode(std::string& s); Endianness DetectEndianness(); + + void Split(std::vector<std::string>& result, + const std::string& source, + char delimiter); } }
--- a/OrthancServer/FromDcmtkBridge.cpp Thu Aug 29 17:59:09 2013 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Fri Aug 30 16:09:19 2013 +0200 @@ -706,13 +706,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; @@ -729,7 +729,53 @@ 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 > sequence->card()) + { + return false; + } + + current = sequence->getItem(it->second); + assert(current != NULL); + } + + return GetTagValueInternal(value, *current, tag); }
--- a/OrthancServer/FromDcmtkBridge.h Thu Aug 29 17:59:09 2013 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Fri Aug 30 16:09:19 2013 +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/RadiotherapyRestApi.cpp Thu Aug 29 17:59:09 2013 +0200 +++ b/OrthancServer/RadiotherapyRestApi.cpp Fri Aug 30 16:09:19 2013 +0200 @@ -40,6 +40,8 @@ ServerContext& context = contextApi.GetContext() +// DICOM tags for RT-STRUCT + #define REFERENCED_STUDY_SEQUENCE "0008,1110" #define REFERENCED_SOP_INSTANCE_UID "0008,1155" #define FRAME_OF_REFERENCE_UID "0020,0052" @@ -51,6 +53,12 @@ #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" + namespace Orthanc { @@ -208,13 +216,15 @@ bool found = false; 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) && - content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i].isMember(ROI_NAME) && - content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString() == call.GetUriComponent("roi", "")) + 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() == call.GetUriComponent("roi", "")) { result["Number"] = call.GetUriComponent("roi", ""); - result["Name"] = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NAME]["Value"].asString(); - result["GenerationAlgorithm"] = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_GENERATION_ALGORITHM]["Value"].asString(); + result["Name"] = roi[ROI_NAME]["Value"].asString(); + result["GenerationAlgorithm"] = roi[ROI_GENERATION_ALGORITHM]["Value"].asString(); found = true; } } @@ -226,13 +236,112 @@ found = false; + boost::mutex::scoped_lock lock(context.GetDicomFileMutex()); + ParsedDicomFile& dicom = context.GetDicomFile(series["Instances"][0].asString()); + for (Json::Value::ArrayIndex i = 0; i < content[ROI_CONTOUR_SEQUENCE]["Value"].size(); i++) { - if (content[ROI_CONTOUR_SEQUENCE]["Value"][i].isMember(REFERENCED_ROI_NUMBER) && - content[ROI_CONTOUR_SEQUENCE]["Value"][i].isMember(ROI_DISPLAY_COLOR) && - content[ROI_CONTOUR_SEQUENCE]["Value"][i][REFERENCED_ROI_NUMBER]["Value"].asString() == call.GetUriComponent("roi", "")) + 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() == call.GetUriComponent("roi", "")) { - result["DisplayColor"] = content[ROI_CONTOUR_SEQUENCE]["Value"][i][ROI_DISPLAY_COLOR]["Value"].asString(); + std::vector<std::string> color; + Toolbox::Split(color, contour[ROI_DISPLAY_COLOR]["Value"].asString(), '\\'); + + result["Points"] = Json::objectValue; + result["ClosedPlanar"] = Json::objectValue; + result["DisplayColor"] = Json::arrayValue; + for (size_t k = 0; k < color.size(); k++) + { + result["DisplayColor"].append(boost::lexical_cast<int>(color[k])); + } + + for (Json::Value::ArrayIndex j = 0; j < contour[CONTOUR_SEQUENCE]["Value"].size(); j++) + { + const Json::Value& contourSequence = contour[CONTOUR_SEQUENCE]["Value"][j]; + + if (contourSequence.isMember(CONTOUR_IMAGE_SEQUENCE) && + contourSequence.isMember(CONTOUR_GEOMETRIC_TYPE) && + contourSequence.isMember(NUMBER_OF_CONTOUR_POINTS) && + contourSequence.isMember(CONTOUR_DATA) && + contourSequence[CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && + contourSequence[CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID)) + { + const std::string type = contourSequence[CONTOUR_GEOMETRIC_TYPE]["Value"].asString(); + if (type != "POINT" && type != "CLOSED_PLANAR") + { + continue; + } + + const std::string uid = (contourSequence[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) + { + continue; + } + + unsigned int countPoints = boost::lexical_cast<unsigned int> + (contourSequence[NUMBER_OF_CONTOUR_POINTS]["Value"].asString()); + if (countPoints <= 0) + { + continue; + } + + ParsedDicomFile::SequencePath path; + path.push_back(std::make_pair(DicomTag(0x3006, 0x0039 /* ROIContourSequence */), i)); + path.push_back(std::make_pair(DicomTag(0x3006, 0x0040 /* ContourSequence */), j)); + + std::string contourData; + dicom.GetTagValue(contourData, path, DicomTag(0x3006, 0x0050 /* ContourData */)); + + std::vector<std::string> points; + Toolbox::Split(points, contourData, '\\'); + + Json::Value* target; + Json::Value item = Json::arrayValue; + + if (type == "POINT" && + countPoints == 1 && + points.size() == 3) + { + target = &result["Points"]; + item.append(boost::lexical_cast<float>(points[0])); + item.append(boost::lexical_cast<float>(points[1])); + item.append(boost::lexical_cast<float>(points[2])); + } + else if (type == "CLOSED_PLANAR" && + points.size() == 3 * countPoints) + { + target = &result["ClosedPlanar"]; + for (size_t k = 0; k < countPoints; k++) + { + Json::Value p = Json::arrayValue; + p.append(boost::lexical_cast<float>(points[3 * k])); + p.append(boost::lexical_cast<float>(points[3 * k + 1])); + p.append(boost::lexical_cast<float>(points[3 * k + 2])); + item.append(p); + } + } + else + { + continue; + } + + if (!target->isMember(instance.front())) + { + (*target) [instance.front()] = Json::arrayValue; + } + + (*target) [instance.front()].append(item); + } + } + found = true; } }
--- a/UnitTests/main.cpp Thu Aug 29 17:59:09 2013 +0200 +++ b/UnitTests/main.cpp Fri Aug 30 16:09:19 2013 +0200 @@ -480,6 +480,47 @@ } +TEST(Toolbox, Split) +{ + std::vector<std::string> s; + + Toolbox::Split(s, "", '|'); + ASSERT_EQ(0, s.size()); + + Toolbox::Split(s, "aaaaa", '|'); + ASSERT_EQ(1, s.size()); + ASSERT_EQ("aaaaa", s[0]); + + Toolbox::Split(s, "aaa|aa", '|'); + ASSERT_EQ(2, s.size()); + ASSERT_EQ("aaa", s[0]); + ASSERT_EQ("aa", s[1]); + + Toolbox::Split(s, "a|aa|ab", '|'); + ASSERT_EQ(3, s.size()); + ASSERT_EQ("a", s[0]); + ASSERT_EQ("aa", s[1]); + ASSERT_EQ("ab", s[2]); + + Toolbox::Split(s, "||ab", '|'); + ASSERT_EQ(3, s.size()); + ASSERT_EQ("", s[0]); + ASSERT_EQ("", s[1]); + ASSERT_EQ("ab", s[2]); + + Toolbox::Split(s, "|", '|'); + ASSERT_EQ(2, s.size()); + ASSERT_EQ("", s[0]); + ASSERT_EQ("", s[1]); + + Toolbox::Split(s, "||", '|'); + ASSERT_EQ(3, s.size()); + ASSERT_EQ("", s[0]); + ASSERT_EQ("", s[1]); + ASSERT_EQ("", s[2]); +} + + int main(int argc, char **argv) { // Initialize Google's logging library.