Mercurial > hg > orthanc-stl
changeset 7:e3e59de705f6
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 18 Jul 2023 15:10:21 +0200 |
parents | c02d12eb34d4 |
children | d1267c6c33e1 |
files | Sources/Plugin.cpp |
diffstat | 1 files changed, 170 insertions(+), 146 deletions(-) [+] |
line wrap: on
line diff
--- a/Sources/Plugin.cpp Tue Jul 18 14:45:20 2023 +0200 +++ b/Sources/Plugin.cpp Tue Jul 18 15:10:21 2023 +0200 @@ -1016,6 +1016,68 @@ bool EncodeStructureSetMesh(std::string& stl, + vtkImageData* volume, + unsigned int resolution, + bool smooth) +{ + if (volume == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + vtkNew<vtkImageResize> resize; + resize->SetOutputDimensions(resolution, resolution, resolution); + resize->SetInputData(volume); + resize->Update(); + + vtkNew<vtkImageConstantPad> padding; + padding->SetConstant(0); + padding->SetOutputNumberOfScalarComponents(1); + padding->SetOutputWholeExtent(-1, resolution, -1, resolution, -1, resolution); + padding->SetInputData(resize->GetOutput()); + padding->Update(); + + double range[2]; + padding->GetOutput()->GetScalarRange(range); + + const double isoValue = (range[0] + range[1]) / 2.0; + + vtkNew<vtkMarchingCubes> surface; + surface->SetInputData(padding->GetOutput()); + surface->ComputeNormalsOn(); + surface->SetValue(0, isoValue); + surface->Update(); + + if (smooth) + { + vtkNew<vtkSmoothPolyDataFilter> smoothFilter; + // Apply volume smoothing + // https://examples.vtk.org/site/Cxx/PolyData/SmoothPolyDataFilter/ + smoothFilter->SetInputConnection(surface->GetOutputPort()); + smoothFilter->SetNumberOfIterations(15); + smoothFilter->SetRelaxationFactor(0.1); + smoothFilter->FeatureEdgeSmoothingOff(); + smoothFilter->BoundarySmoothingOn(); + smoothFilter->Update(); + + vtkNew<vtkPolyDataNormals> normalGenerator; + normalGenerator->SetInputConnection(smoothFilter->GetOutputPort()); + normalGenerator->ComputePointNormalsOn(); + normalGenerator->ComputeCellNormalsOn(); + normalGenerator->Update(); + + EncodeSTL(stl, *normalGenerator->GetOutput()); + } + else + { + EncodeSTL(stl, *surface->GetOutput()); + } + + return true; +} + + +bool EncodeStructureSetMesh(std::string& stl, const StructureSet& structureSet, const std::set<std::string>& roiNames, unsigned int resolution, @@ -1095,63 +1157,15 @@ Orthanc::ImageProcessing::FillPolygon(filler, points); } - vtkNew<vtkImageResize> resize; - resize->SetOutputDimensions(resolution, resolution, resolution); - resize->SetInputData(volume.Get()); - resize->Update(); - - resize->GetOutput()->SetSpacing( + volume->SetSpacing( extent.GetWidth() / static_cast<double>(resolution), extent.GetHeight() / static_cast<double>(resolution), - (structureSet.GetMaxProjectionAlongNormal() - structureSet.GetMinProjectionAlongNormal()) / static_cast<double>(resolution)); + (structureSet.GetMaxProjectionAlongNormal() - structureSet.GetMinProjectionAlongNormal()) / static_cast<double>(depth)); // TODO - // resize->GetOutput()->SetOrigin() - - vtkNew<vtkImageConstantPad> padding; - padding->SetConstant(0); - padding->SetOutputNumberOfScalarComponents(1); - padding->SetOutputWholeExtent(-1, resolution, -1, resolution, -1, resolution); - padding->SetInputData(resize->GetOutput()); - padding->Update(); - - double range[2]; - padding->GetOutput()->GetScalarRange(range); - - const double isoValue = (range[0] + range[1]) / 2.0; - - vtkNew<vtkMarchingCubes> surface; - surface->SetInputData(padding->GetOutput()); - surface->ComputeNormalsOn(); - surface->SetValue(0, isoValue); - surface->Update(); + // volume->SetOrigin() - if (smooth) - { - vtkNew<vtkSmoothPolyDataFilter> smoothFilter; - // Apply volume smoothing - // https://examples.vtk.org/site/Cxx/PolyData/SmoothPolyDataFilter/ - smoothFilter->SetInputConnection(surface->GetOutputPort()); - smoothFilter->SetNumberOfIterations(15); - smoothFilter->SetRelaxationFactor(0.1); - smoothFilter->FeatureEdgeSmoothingOff(); - smoothFilter->BoundarySmoothingOn(); - smoothFilter->Update(); - - vtkNew<vtkPolyDataNormals> normalGenerator; - normalGenerator->SetInputConnection(smoothFilter->GetOutputPort()); - normalGenerator->ComputePointNormalsOn(); - normalGenerator->ComputeCellNormalsOn(); - normalGenerator->Update(); - - EncodeSTL(stl, *normalGenerator->GetOutput()); - } - else - { - EncodeSTL(stl, *surface->GetOutput()); - } - - return true; + return EncodeStructureSetMesh(stl, volume.Get(), resolution, smooth); } @@ -1218,15 +1232,93 @@ } -void Encode(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +static void CallCreateDicom(Json::Value& answer, + const std::string& stl, + const Json::Value& body, + const std::string& parentStudy, + const std::string& defaultSeriesDescription, + const std::string& defaultFrameOfReferenceUid, + const std::string& defaultTitle) +{ + static const char* const KEY_TAGS = "Tags"; + + Json::Value normalized = Json::objectValue; + + if (body.isMember(KEY_TAGS)) + { + const Json::Value& tags = body[KEY_TAGS]; + + if (tags.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "Tags must be provided as a JSON object"); + } + + std::vector<std::string> keys = tags.getMemberNames(); + for (size_t i = 0; i < keys.size(); i++) + { + const Orthanc::DicomTag tag = Orthanc::FromDcmtkBridge::ParseTag(keys[i]); + normalized[tag.Format()] = tags[keys[i]]; + } + } + + if (!normalized.isMember(Orthanc::DICOM_TAG_SERIES_DESCRIPTION.Format())) + { + normalized[Orthanc::DICOM_TAG_SERIES_DESCRIPTION.Format()] = defaultSeriesDescription; + } + + AddDefaultTagValue(normalized, Orthanc::DICOM_TAG_SERIES_NUMBER, "1"); + AddDefaultTagValue(normalized, Orthanc::DICOM_TAG_FRAME_OF_REFERENCE_UID, defaultFrameOfReferenceUid); + AddDefaultTagValue(normalized, Orthanc::DICOM_TAG_INSTANCE_NUMBER, "1"); + + AddDefaultTagValue(normalized, DCM_BurnedInAnnotation, "NO"); + AddDefaultTagValue(normalized, DCM_DeviceSerialNumber, ORTHANC_STL_VERSION); + AddDefaultTagValue(normalized, DCM_DocumentTitle, defaultTitle); + AddDefaultTagValue(normalized, DCM_Manufacturer, "Orthanc STL plugin"); + AddDefaultTagValue(normalized, DCM_ManufacturerModelName, "Orthanc STL plugin"); + AddDefaultTagValue(normalized, DCM_PositionReferenceIndicator, ""); + AddDefaultTagValue(normalized, DCM_SoftwareVersions, ORTHANC_STL_VERSION); + AddDefaultTagValue(normalized, DCM_ConceptNameCodeSequence, ""); + + std::string date, time; + Orthanc::SystemToolbox::GetNowDicom(date, time, true /* use UTC time (not local time) */); + AddDefaultTagValue(normalized, DCM_AcquisitionDateTime, date + time); + + const Orthanc::DicomTag MEASUREMENT_UNITS_CODE_SEQUENCE(DCM_MeasurementUnitsCodeSequence.getGroup(), + DCM_MeasurementUnitsCodeSequence.getElement()); + + if (!normalized.isMember(MEASUREMENT_UNITS_CODE_SEQUENCE.Format())) + { + Json::Value item; + item["CodeValue"] = "mm"; + item["CodingSchemeDesignator"] = "UCUM"; + item["CodeMeaning"] = defaultTitle; + + normalized[MEASUREMENT_UNITS_CODE_SEQUENCE.Format()].append(item); + } + + std::string content; + Orthanc::Toolbox::EncodeDataUriScheme(content, Orthanc::MIME_STL, stl); + + Json::Value create; + create["Content"] = content; + create["Parent"] = parentStudy; + create["Tags"] = normalized; + + if (!OrthancPlugins::RestApiPost(answer, "/tools/create-dicom", create.toStyledString(), false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "Cannot create DICOM from STL"); + } +} + + +void EncodeStructureSet(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { static const char* const KEY_INSTANCE = "Instance"; static const char* const KEY_RESOLUTION = "Resolution"; static const char* const KEY_ROI_NAMES = "RoiNames"; static const char* const KEY_SMOOTH = "Smooth"; - static const char* const KEY_TAGS = "Tags"; if (request->method != OrthancPluginHttpMethod_Post) { @@ -1262,116 +1354,48 @@ } else { - std::string content; - Orthanc::Toolbox::EncodeDataUriScheme(content, "model/stl", stl); + std::string seriesDescription; - Json::Value normalized = Json::objectValue; - - if (body.isMember(KEY_TAGS)) + if (dicom->GetTagValue(seriesDescription, Orthanc::DICOM_TAG_SERIES_DESCRIPTION)) { - const Json::Value& tags = body[KEY_TAGS]; - if (tags.type() != Json::objectValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "Tags must be provided as a JSON object"); - } - - std::vector<std::string> keys = tags.getMemberNames(); - for (size_t i = 0; i < keys.size(); i++) - { - const Orthanc::DicomTag tag = Orthanc::FromDcmtkBridge::ParseTag(keys[i]); - normalized[tag.Format()] = tags[keys[i]]; - } + seriesDescription += ": "; + } + else + { + seriesDescription.clear(); } - if (!normalized.isMember(Orthanc::DICOM_TAG_SERIES_DESCRIPTION.Format())) + bool first = true; + for (std::set<std::string>::const_iterator it = roiNames.begin(); it != roiNames.end(); ++it) { - std::string description; - - if (dicom->GetTagValue(description, Orthanc::DICOM_TAG_SERIES_DESCRIPTION)) + if (first) { - description += ": "; + first = false; } else { - description.clear(); + seriesDescription += ", "; } - bool first = true; - for (std::set<std::string>::const_iterator it = roiNames.begin(); it != roiNames.end(); ++it) - { - if (first) - { - first = false; - } - else - { - description += ", "; - } - - description += *it; - } - - normalized[Orthanc::DICOM_TAG_SERIES_DESCRIPTION.Format()] = description; + seriesDescription += *it; } - AddDefaultTagValue(normalized, Orthanc::DICOM_TAG_SERIES_NUMBER, "1"); - - std::string s; + std::string frameOfReferenceUid; if (structureSet.HasFrameOfReferenceUid()) { - s = structureSet.GetFrameOfReferenceUid(); + frameOfReferenceUid = structureSet.GetFrameOfReferenceUid(); } else { - s = Orthanc::FromDcmtkBridge::GenerateUniqueIdentifier(Orthanc::ResourceType_Instance); + frameOfReferenceUid = Orthanc::FromDcmtkBridge::GenerateUniqueIdentifier(Orthanc::ResourceType_Instance); } - AddDefaultTagValue(normalized, Orthanc::DICOM_TAG_FRAME_OF_REFERENCE_UID, s); - AddDefaultTagValue(normalized, Orthanc::DICOM_TAG_INSTANCE_NUMBER, "1"); - - const std::string title = "STL model generated from DICOM RT-STRUCT"; - - AddDefaultTagValue(normalized, DCM_BurnedInAnnotation, "NO"); - AddDefaultTagValue(normalized, DCM_DeviceSerialNumber, ORTHANC_STL_VERSION); - AddDefaultTagValue(normalized, DCM_DocumentTitle, title); - AddDefaultTagValue(normalized, DCM_Manufacturer, "Orthanc STL plugin"); - AddDefaultTagValue(normalized, DCM_ManufacturerModelName, "Orthanc STL plugin"); - AddDefaultTagValue(normalized, DCM_PositionReferenceIndicator, ""); - AddDefaultTagValue(normalized, DCM_SoftwareVersions, ORTHANC_STL_VERSION); - AddDefaultTagValue(normalized, DCM_ConceptNameCodeSequence, ""); - - std::string date, time; - Orthanc::SystemToolbox::GetNowDicom(date, time, true /* use UTC time (not local time) */); - AddDefaultTagValue(normalized, DCM_AcquisitionDateTime, date + time); - - const Orthanc::DicomTag MEASUREMENT_UNITS_CODE_SEQUENCE(DCM_MeasurementUnitsCodeSequence.getGroup(), - DCM_MeasurementUnitsCodeSequence.getElement()); + Json::Value answer; + CallCreateDicom(answer, stl, body, structureSet.HashStudy(), seriesDescription, + frameOfReferenceUid, "STL model generated from DICOM RT-STRUCT"); - if (!normalized.isMember(MEASUREMENT_UNITS_CODE_SEQUENCE.Format())) - { - Json::Value item; - item["CodeValue"] = "mm"; - item["CodingSchemeDesignator"] = "UCUM"; - item["CodeMeaning"] = title; - - normalized[MEASUREMENT_UNITS_CODE_SEQUENCE.Format()].append(item); - } - - Json::Value create; - create["Content"] = content; - create["Parent"] = structureSet.HashStudy(); - create["Tags"] = normalized; - - Json::Value answer; - if (OrthancPlugins::RestApiPost(answer, "/tools/create-dicom", create.toStyledString(), false)) - { - std::string s = answer.toStyledString(); - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), Orthanc::MIME_JSON); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "Cannot create DICOM from STL"); - } + std::string s = answer.toStyledString(); + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), Orthanc::MIME_JSON); } } @@ -1538,7 +1562,7 @@ if (hasCreateDicomStl_) { - OrthancPlugins::RegisterRestCallback<Encode>("/stl/encode", true); + OrthancPlugins::RegisterRestCallback<EncodeStructureSet>("/stl/encode", true); } // Extend the default Orthanc Explorer with custom JavaScript for STL