Mercurial > hg > orthanc-stone
view Framework/Toolbox/DicomStructureSet2.cpp @ 1114:33b0a762e98a toa2019110401
Added support for signed 16-bit pixels/voxels in multiframe loader
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Mon, 04 Nov 2019 15:11:23 +0100 |
parents | 29f5f2031310 |
children | 2d8ab34c8c91 |
line wrap: on
line source
/** * Stone of Orthanc * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2019 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * 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 * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 #include "DicomStructureSet2.h" #include "../Toolbox/LinearAlgebra.h" #include "../StoneException.h" #include <Core/Logging.h> #include <Core/OrthancException.h> #include <Core/Toolbox.h> #include <Core/DicomFormat/DicomTag.h> #include <Plugins/Samples/Common/FullOrthancDataset.h> #include <Plugins/Samples/Common/DicomDatasetReader.h> namespace OrthancStone { static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042); static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016); static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040); static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050); static const OrthancPlugins::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046); static const OrthancPlugins::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); static const OrthancPlugins::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039); static const OrthancPlugins::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a); static const OrthancPlugins::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026); static const OrthancPlugins::DicomTag DICOM_TAG_RT_ROI_INTERPRETED_TYPE(0x3006, 0x00a4); static const OrthancPlugins::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080); static const OrthancPlugins::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020); static inline uint8_t ConvertAndClipToByte(double v) { if (v < 0) { return 0; } else if (v >= 255) { return 255; } else { return static_cast<uint8_t>(v); } } static bool ReadDicomToVector(Vector& target, const OrthancPlugins::IDicomDataset& dataset, const OrthancPlugins::DicomPath& tag) { std::string value; return (dataset.GetStringValue(value, tag) && LinearAlgebra::ParseVector(target, value)); } void DicomPathToString(std::string& s, const OrthancPlugins::DicomPath& dicomPath) { std::stringstream tmp; for (size_t i = 0; i < dicomPath.GetPrefixLength(); ++i) { OrthancPlugins::DicomTag tag = dicomPath.GetPrefixTag(i); // We use this other object to be able to use GetMainTagsName // and Format Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement()); size_t index = dicomPath.GetPrefixIndex(i); tmp << tag2.GetMainTagsName() << " (" << tag2.Format() << ") [" << index << "] / "; } const OrthancPlugins::DicomTag& tag = dicomPath.GetFinalTag(); Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement()); tmp << tag2.GetMainTagsName() << " (" << tag2.Format() << ")"; s = tmp.str(); } std::ostream& operator<<(std::ostream& s, const OrthancPlugins::DicomPath& dicomPath) { std::string tmp; DicomPathToString(tmp, dicomPath); s << tmp; return s; } DicomStructureSet2::DicomStructureSet2() { } DicomStructureSet2::~DicomStructureSet2() { } void DicomStructureSet2::SetContents(const OrthancPlugins::FullOrthancDataset& tags) { FillStructuresFromDataset(tags); ComputeDependentProperties(); } void DicomStructureSet2::ComputeDependentProperties() { for (size_t i = 0; i < structures_.size(); ++i) { structures_[i].ComputeDependentProperties(); } } void DicomStructureSet2::FillStructuresFromDataset(const OrthancPlugins::FullOrthancDataset& tags) { OrthancPlugins::DicomDatasetReader reader(tags); // a few sanity checks size_t count = 0, tmp = 0; // DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE (0x3006, 0x0080); // DICOM_TAG_ROI_CONTOUR_SEQUENCE (0x3006, 0x0039); // DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE (0x3006, 0x0020); if (!tags.GetSequenceSize(count, DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE) || !tags.GetSequenceSize(tmp, DICOM_TAG_ROI_CONTOUR_SEQUENCE) || tmp != count || !tags.GetSequenceSize(tmp, DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE) || tmp != count) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } // let's now parse the structures stored in the dicom file // DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE (0x3006, 0x0080) // DICOM_TAG_RT_ROI_INTERPRETED_TYPE (0x3006, 0x00a4) // DICOM_TAG_ROI_DISPLAY_COLOR (0x3006, 0x002a) // DICOM_TAG_ROI_NAME (0x3006, 0x0026) structures_.resize(count); for (size_t i = 0; i < count; i++) { // (0x3006, 0x0080)[i]/(0x3006, 0x00a4) structures_[i].interpretation_ = reader.GetStringValue (OrthancPlugins::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, DICOM_TAG_RT_ROI_INTERPRETED_TYPE), "No interpretation"); // (0x3006, 0x0020)[i]/(0x3006, 0x0026) structures_[i].name_ = reader.GetStringValue (OrthancPlugins::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, DICOM_TAG_ROI_NAME), "No name"); Vector color; // (0x3006, 0x0039)[i]/(0x3006, 0x002a) if (ReadDicomToVector(color, tags, OrthancPlugins::DicomPath( DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_ROI_DISPLAY_COLOR)) && color.size() == 3) { structures_[i].red_ = ConvertAndClipToByte(color[0]); structures_[i].green_ = ConvertAndClipToByte(color[1]); structures_[i].blue_ = ConvertAndClipToByte(color[2]); } else { structures_[i].red_ = 255; structures_[i].green_ = 0; structures_[i].blue_ = 0; } size_t countSlices; // DICOM_TAG_ROI_CONTOUR_SEQUENCE (0x3006, 0x0039); // DICOM_TAG_CONTOUR_SEQUENCE (0x3006, 0x0040); if (!tags.GetSequenceSize(countSlices, OrthancPlugins::DicomPath( DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE))) { LOG(WARNING) << "DicomStructureSet2::SetContents | structure \"" << structures_[i].name_ << "\" has no slices!"; countSlices = 0; } LOG(INFO) << "New RT structure: \"" << structures_[i].name_ << "\" with interpretation \"" << structures_[i].interpretation_ << "\" containing " << countSlices << " slices (color: " << static_cast<int>(structures_[i].red_) << "," << static_cast<int>(structures_[i].green_) << "," << static_cast<int>(structures_[i].blue_) << ")"; // These temporary variables avoid allocating many vectors in the loop below // (0x3006, 0x0039)[i]/(0x3006, 0x0040)[0]/(0x3006, 0x0046) OrthancPlugins::DicomPath countPointsPath( DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE, 0, DICOM_TAG_NUMBER_OF_CONTOUR_POINTS); OrthancPlugins::DicomPath geometricTypePath( DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE, 0, DICOM_TAG_CONTOUR_GEOMETRIC_TYPE); OrthancPlugins::DicomPath imageSequencePath( DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE, 0, DICOM_TAG_CONTOUR_IMAGE_SEQUENCE); // (3006,0039)[i] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155) OrthancPlugins::DicomPath referencedInstancePath( DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE, 0, DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, DICOM_TAG_REFERENCED_SOP_INSTANCE_UID); OrthancPlugins::DicomPath contourDataPath( DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE, 0, DICOM_TAG_CONTOUR_DATA); for (size_t j = 0; j < countSlices; j++) { unsigned int countPoints = 0; countPointsPath.SetPrefixIndex(1, j); if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath)) { std::string s; DicomPathToString(s, countPointsPath); LOG(ERROR) << "Dicom path " << s << " is not valid (should contain an unsigned integer)"; throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices"; geometricTypePath.SetPrefixIndex(1, j); std::string type = reader.GetMandatoryStringValue(geometricTypePath); if (type != "CLOSED_PLANAR") { // TODO: support points!! LOG(WARNING) << "Ignoring contour with geometry type: " << type; continue; } size_t size = 0; imageSequencePath.SetPrefixIndex(1, j); if (!tags.GetSequenceSize(size, imageSequencePath) || size != 1) { LOG(ERROR) << "The ContourImageSequence sequence (tag 3006,0016) must be present and contain one entry."; throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } referencedInstancePath.SetPrefixIndex(1, j); std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath); contourDataPath.SetPrefixIndex(1, j); std::string slicesData = reader.GetMandatoryStringValue(contourDataPath); Vector points; if (!LinearAlgebra::ParseVector(points, slicesData) || points.size() != 3 * countPoints) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } // seen in real world if (Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "") { LOG(ERROR) << "WARNING. The following Dicom tag (Referenced SOP Instance UID) contains an empty value : // (3006,0039)[" << i << "] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)"; } DicomStructurePolygon2 polygon(sopInstanceUid,type); polygon.Reserve(countPoints); for (size_t k = 0; k < countPoints; k++) { Vector v(3); v[0] = points[3 * k]; v[1] = points[3 * k + 1]; v[2] = points[3 * k + 2]; polygon.AddPoint(v); } structures_[i].AddPolygon(polygon); } } } void DicomStructureSet2::Clear() { structures_.clear(); } } #endif // BGO_ENABLE_DICOMSTRUCTURESETLOADER2