Mercurial > hg > orthanc-stl
comparison Sources/Plugin.cpp @ 29:62abf3c523f9
big endian support, retrieve axes from referenced volume
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 04 Apr 2024 16:29:01 +0200 |
parents | 410003c50b17 |
children | 3570c23764d4 |
comparison
equal
deleted
inserted
replaced
28:410003c50b17 | 29:62abf3c523f9 |
---|---|
51 | 51 |
52 #include <nifti1_io.h> | 52 #include <nifti1_io.h> |
53 | 53 |
54 #define ORTHANC_PLUGIN_NAME "stl" | 54 #define ORTHANC_PLUGIN_NAME "stl" |
55 | 55 |
56 static const char* const STL_SOP_CLASS_UID = "1.2.840.10008.5.1.4.1.1.104.3"; | |
57 | |
58 | 56 |
59 // Forward declaration | 57 // Forward declaration |
60 void ReadStaticAsset(std::string& target, | 58 void ReadStaticAsset(std::string& target, |
61 const std::string& path); | 59 const std::string& path); |
62 | 60 |
160 | 158 |
161 #include <dcmtk/dcmdata/dcdeftag.h> | 159 #include <dcmtk/dcmdata/dcdeftag.h> |
162 #include <dcmtk/dcmdata/dcfilefo.h> | 160 #include <dcmtk/dcmdata/dcfilefo.h> |
163 #include <dcmtk/dcmdata/dcitem.h> | 161 #include <dcmtk/dcmdata/dcitem.h> |
164 #include <dcmtk/dcmdata/dcsequen.h> | 162 #include <dcmtk/dcmdata/dcsequen.h> |
163 #include <dcmtk/dcmdata/dcuid.h> | |
165 | 164 |
166 class Extent2D : public boost::noncopyable | 165 class Extent2D : public boost::noncopyable |
167 { | 166 { |
168 private: | 167 private: |
169 bool isEmpty_; | 168 bool isEmpty_; |
345 double GetZ() const | 344 double GetZ() const |
346 { | 345 { |
347 return z_; | 346 return z_; |
348 } | 347 } |
349 | 348 |
349 double ComputeSquaredNorm() const | |
350 { | |
351 return x_ * x_ + y_ * y_ + z_ * z_; | |
352 } | |
353 | |
350 double ComputeNorm() const | 354 double ComputeNorm() const |
351 { | 355 { |
352 return sqrt(x_ * x_ + y_ * y_ + z_ * z_); | 356 return sqrt(ComputeSquaredNorm()); |
353 } | 357 } |
354 | 358 |
355 void Normalize() | 359 void Normalize() |
356 { | 360 { |
357 double norm = ComputeNorm(); | 361 double norm = ComputeNorm(); |
373 | 377 |
374 static double DotProduct(const Vector3D& a, | 378 static double DotProduct(const Vector3D& a, |
375 const Vector3D& b) | 379 const Vector3D& b) |
376 { | 380 { |
377 return a.GetX() * b.GetX() + a.GetY() * b.GetY() + a.GetZ() * b.GetZ(); | 381 return a.GetX() * b.GetX() + a.GetY() * b.GetY() + a.GetZ() * b.GetZ(); |
382 } | |
383 | |
384 static bool AreParallel(const Vector3D& a, | |
385 const Vector3D& b) | |
386 { | |
387 if (IsNear(a.ComputeSquaredNorm(), 1) && | |
388 IsNear(b.ComputeSquaredNorm(), 1)) | |
389 { | |
390 return IsNear(std::abs(Vector3D::DotProduct(a, b)), 1); | |
391 } | |
392 else | |
393 { | |
394 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, | |
395 "Only applicable to normalized vectors"); | |
396 } | |
378 } | 397 } |
379 }; | 398 }; |
380 | 399 |
381 | 400 |
382 | 401 |
676 assert(polygons_[i] != NULL); | 695 assert(polygons_[i] != NULL); |
677 delete polygons_[i]; | 696 delete polygons_[i]; |
678 } | 697 } |
679 } | 698 } |
680 | 699 |
681 const std::string& GetPatient() const | 700 const std::string& GetPatientId() const |
682 { | 701 { |
683 return patientId_; | 702 return patientId_; |
684 } | 703 } |
685 | 704 |
686 const std::string& GetStudyInstanceUid() const | 705 const std::string& GetStudyInstanceUid() const |
749 double slicesSpacing_; | 768 double slicesSpacing_; |
750 double minProjectionAlongNormal_; | 769 double minProjectionAlongNormal_; |
751 double maxProjectionAlongNormal_; | 770 double maxProjectionAlongNormal_; |
752 size_t slicesCount_; | 771 size_t slicesCount_; |
753 | 772 |
754 static bool AreNormalsParallel(const Vector3D& a, | |
755 const Vector3D& b) | |
756 { | |
757 return IsNear(std::abs(Vector3D::DotProduct(a, b)), 1); | |
758 } | |
759 | |
760 bool LookupProjectionIndex(size_t& index, | 773 bool LookupProjectionIndex(size_t& index, |
761 double z) const | 774 double z) const |
762 { | 775 { |
763 if (slicesCount_ == 0) | 776 if (slicesCount_ == 0) |
764 { | 777 { |
815 { | 828 { |
816 isValid = true; | 829 isValid = true; |
817 slicesNormal_ = normal; | 830 slicesNormal_ = normal; |
818 } | 831 } |
819 | 832 |
820 if (AreNormalsParallel(normal, slicesNormal_)) | 833 if (Vector3D::AreParallel(normal, slicesNormal_)) |
821 { | 834 { |
822 // This is a valid slice (it is parallel to the normal) | 835 // This is a valid slice (it is parallel to the normal) |
823 const Vector3D& point = structures.GetPolygon(i).GetPoint(0); | 836 const Vector3D& point = structures.GetPolygon(i).GetPoint(0); |
824 projections.push_back(Vector3D::DotProduct(point, slicesNormal_)); | 837 projections.push_back(Vector3D::DotProduct(point, slicesNormal_)); |
825 } | 838 } |
1000 bool ProjectAlongNormal(double& z, | 1013 bool ProjectAlongNormal(double& z, |
1001 const StructurePolygon& polygon) const | 1014 const StructurePolygon& polygon) const |
1002 { | 1015 { |
1003 Vector3D normal; | 1016 Vector3D normal; |
1004 if (polygon.IsCoplanar(normal) && | 1017 if (polygon.IsCoplanar(normal) && |
1005 AreNormalsParallel(normal, slicesNormal_)) | 1018 Vector3D::AreParallel(normal, slicesNormal_)) |
1006 { | 1019 { |
1007 z = Vector3D::DotProduct(polygon.GetPoint(0), slicesNormal_); | 1020 z = Vector3D::DotProduct(polygon.GetPoint(0), slicesNormal_); |
1008 return true; | 1021 return true; |
1009 } | 1022 } |
1010 else | 1023 else |
1062 } | 1075 } |
1063 } | 1076 } |
1064 }; | 1077 }; |
1065 | 1078 |
1066 | 1079 |
1080 static void WriteFloat(Orthanc::ChunkedBuffer& buffer, | |
1081 float value, | |
1082 Orthanc::Endianness endianness) | |
1083 { | |
1084 switch (endianness) | |
1085 { | |
1086 case Orthanc::Endianness_Little: | |
1087 buffer.AddChunk(&value, sizeof(float)); | |
1088 break; | |
1089 | |
1090 case Orthanc::Endianness_Big: | |
1091 { | |
1092 uint8_t tmp[4]; | |
1093 tmp[0] = reinterpret_cast<uint8_t*>(&value) [3]; | |
1094 tmp[1] = reinterpret_cast<uint8_t*>(&value) [2]; | |
1095 tmp[2] = reinterpret_cast<uint8_t*>(&value) [1]; | |
1096 tmp[3] = reinterpret_cast<uint8_t*>(&value) [0]; | |
1097 buffer.AddChunk(&tmp, sizeof(float)); | |
1098 break; | |
1099 } | |
1100 | |
1101 default: | |
1102 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
1103 } | |
1104 } | |
1105 | |
1106 | |
1107 static void WriteInteger(Orthanc::ChunkedBuffer& buffer, | |
1108 uint32_t value, | |
1109 Orthanc::Endianness endianness) | |
1110 { | |
1111 switch (endianness) | |
1112 { | |
1113 case Orthanc::Endianness_Little: | |
1114 buffer.AddChunk(&value, sizeof(uint32_t)); | |
1115 break; | |
1116 | |
1117 case Orthanc::Endianness_Big: | |
1118 { | |
1119 uint8_t tmp[4]; | |
1120 tmp[0] = reinterpret_cast<uint8_t*>(&value) [3]; | |
1121 tmp[1] = reinterpret_cast<uint8_t*>(&value) [2]; | |
1122 tmp[2] = reinterpret_cast<uint8_t*>(&value) [1]; | |
1123 tmp[3] = reinterpret_cast<uint8_t*>(&value) [0]; | |
1124 buffer.AddChunk(&tmp, sizeof(uint32_t)); | |
1125 break; | |
1126 } | |
1127 | |
1128 default: | |
1129 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
1130 } | |
1131 } | |
1132 | |
1133 | |
1067 static void EncodeSTL(std::string& target /* out */, | 1134 static void EncodeSTL(std::string& target /* out */, |
1068 vtkPolyData& mesh /* in */) | 1135 vtkPolyData& mesh /* in */) |
1069 { | 1136 { |
1070 // TODO - Conversion to little endian on big endian | 1137 const Orthanc::Endianness endianness = Orthanc::Toolbox::DetectEndianness(); |
1071 | 1138 |
1072 Orthanc::ChunkedBuffer buffer; | 1139 Orthanc::ChunkedBuffer buffer; |
1073 | 1140 |
1074 uint8_t header[80]; | 1141 uint8_t header[80]; |
1075 memset(header, 0, sizeof(header)); | 1142 memset(header, 0, sizeof(header)); |
1076 buffer.AddChunk(header, sizeof(header)); | 1143 buffer.AddChunk(header, sizeof(header)); // This doesn't depend on endianness |
1077 | 1144 |
1078 uint32_t n = mesh.GetNumberOfCells(); | 1145 WriteInteger(buffer, mesh.GetNumberOfCells(), endianness); |
1079 buffer.AddChunk(&n, sizeof(n)); | |
1080 | 1146 |
1081 for (vtkIdType i = 0; i < mesh.GetNumberOfCells(); i++) | 1147 for (vtkIdType i = 0; i < mesh.GetNumberOfCells(); i++) |
1082 { | 1148 { |
1083 vtkCell* cell = mesh.GetCell(i); | 1149 vtkCell* cell = mesh.GetCell(i); |
1084 vtkTriangle* triangle = dynamic_cast<vtkTriangle*>(cell); | 1150 vtkTriangle* triangle = dynamic_cast<vtkTriangle*>(cell); |
1091 triangle->GetPoints()->GetPoint(2, p2); | 1157 triangle->GetPoints()->GetPoint(2, p2); |
1092 | 1158 |
1093 double normal[3]; | 1159 double normal[3]; |
1094 vtkTriangle::ComputeNormal(p0, p1, p2, normal); | 1160 vtkTriangle::ComputeNormal(p0, p1, p2, normal); |
1095 | 1161 |
1096 float d[4 * 3] = { | 1162 WriteFloat(buffer, normal[0], endianness); |
1097 static_cast<float>(normal[0]), static_cast<float>(normal[1]), static_cast<float>(normal[2]), | 1163 WriteFloat(buffer, normal[1], endianness); |
1098 static_cast<float>(p0[0]), static_cast<float>(p0[1]), static_cast<float>(p0[2]), | 1164 WriteFloat(buffer, normal[2], endianness); |
1099 static_cast<float>(p1[0]), static_cast<float>(p1[1]), static_cast<float>(p1[2]), | 1165 WriteFloat(buffer, p0[0], endianness); |
1100 static_cast<float>(p2[0]), static_cast<float>(p2[1]), static_cast<float>(p2[2]) }; | 1166 WriteFloat(buffer, p0[1], endianness); |
1101 buffer.AddChunk(d, sizeof(d)); | 1167 WriteFloat(buffer, p0[2], endianness); |
1168 WriteFloat(buffer, p1[0], endianness); | |
1169 WriteFloat(buffer, p1[1], endianness); | |
1170 WriteFloat(buffer, p1[2], endianness); | |
1171 WriteFloat(buffer, p2[0], endianness); | |
1172 WriteFloat(buffer, p2[1], endianness); | |
1173 WriteFloat(buffer, p2[2], endianness); | |
1102 | 1174 |
1103 uint16_t a = 0; | 1175 uint16_t a = 0; |
1104 buffer.AddChunk(&a, sizeof(a)); | 1176 buffer.AddChunk(&a, sizeof(a)); // This doesn't depend on endianness |
1105 } | 1177 } |
1106 | 1178 |
1107 buffer.Flatten(target); | 1179 buffer.Flatten(target); |
1108 } | 1180 } |
1109 | 1181 |
1168 | 1240 |
1169 return true; | 1241 return true; |
1170 } | 1242 } |
1171 | 1243 |
1172 | 1244 |
1173 bool EncodeStructureSetMesh(std::string& stl, | 1245 static Orthanc::ParsedDicomFile* LoadInstance(const std::string& instanceId) |
1174 const StructureSet& structureSet, | 1246 { |
1175 const StructureSetGeometry& geometry, | 1247 std::string dicom; |
1176 const std::set<std::string>& roiNames, | 1248 |
1177 unsigned int resolution, | 1249 if (!OrthancPlugins::RestApiGetString(dicom, "/instances/" + instanceId + "/file", false)) |
1178 bool smooth) | 1250 { |
1179 { | 1251 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); |
1180 if (resolution < 1) | 1252 } |
1253 else | |
1254 { | |
1255 return new Orthanc::ParsedDicomFile(dicom); | |
1256 } | |
1257 } | |
1258 | |
1259 | |
1260 static void GetReferencedVolumeAxes(Vector3D& axisX, | |
1261 Vector3D& axisY, | |
1262 const StructureSet& structureSet) | |
1263 { | |
1264 // This is a rough guess for the X/Y axes | |
1265 axisX = Vector3D(1, 0, 0); | |
1266 axisY = Vector3D(0, 1, 0); | |
1267 | |
1268 // Look for one instance from the referenced volume | |
1269 const std::string& sopInstanceUid = structureSet.GetPolygon(0).GetReferencedSopInstanceUid(); | |
1270 | |
1271 Json::Value response; | |
1272 if (OrthancPlugins::RestApiPost(response, "/tools/lookup", sopInstanceUid, false)) | |
1273 { | |
1274 if (response.type() != Json::arrayValue) | |
1275 { | |
1276 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); | |
1277 } | |
1278 | |
1279 bool first = true; | |
1280 | |
1281 for (Json::Value::ArrayIndex i = 0; i < response.size(); i++) | |
1282 { | |
1283 if (response[i].type() != Json::objectValue) | |
1284 { | |
1285 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); | |
1286 } | |
1287 | |
1288 if (Orthanc::SerializationToolbox::ReadString(response[i], "Type") == "Instance") | |
1289 { | |
1290 if (first) | |
1291 { | |
1292 const std::string& instanceId = Orthanc::SerializationToolbox::ReadString(response[i], "ID"); | |
1293 std::unique_ptr<Orthanc::ParsedDicomFile> reference(LoadInstance(instanceId)); | |
1294 | |
1295 std::string imageOrientation; | |
1296 if (reference->GetTagValue(imageOrientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT)) | |
1297 { | |
1298 std::vector<std::string> items; | |
1299 Orthanc::Toolbox::TokenizeString(items, imageOrientation, '\\'); | |
1300 | |
1301 double x1, x2, x3, y1, y2, y3; | |
1302 | |
1303 if (items.size() == 6 && | |
1304 MyParseDouble(y1, items[0]) && | |
1305 MyParseDouble(y2, items[1]) && | |
1306 MyParseDouble(y3, items[2]) && | |
1307 MyParseDouble(x1, items[3]) && | |
1308 MyParseDouble(x2, items[4]) && | |
1309 MyParseDouble(x3, items[5])) | |
1310 { | |
1311 axisX = Vector3D(x1, x2, x3); | |
1312 axisY = Vector3D(y1, y2, y3); | |
1313 } | |
1314 } | |
1315 } | |
1316 else | |
1317 { | |
1318 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, | |
1319 "Multiple instances with the same SOP Instance UID"); | |
1320 } | |
1321 } | |
1322 } | |
1323 } | |
1324 } | |
1325 | |
1326 | |
1327 static bool EncodeStructureSetMesh(std::string& stl, | |
1328 const StructureSet& structureSet, | |
1329 const StructureSetGeometry& geometry, | |
1330 const std::set<std::string>& roiNames, | |
1331 unsigned int resolution, | |
1332 bool smooth) | |
1333 { | |
1334 if (resolution < 1 || | |
1335 structureSet.GetPolygonsCount() < 1) | |
1181 { | 1336 { |
1182 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | 1337 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); |
1183 } | 1338 } |
1184 | 1339 |
1185 if (!IsNear(1, geometry.GetSlicesNormal().ComputeNorm())) | 1340 if (!IsNear(1, geometry.GetSlicesNormal().ComputeNorm())) |
1186 { | 1341 { |
1187 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | 1342 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); |
1188 } | 1343 } |
1189 | 1344 |
1190 // TODO - Axes could be retrieved from the referenced CT volume | 1345 Vector3D axisX, axisY; |
1191 Vector3D axisX(1, 0, 0); | 1346 GetReferencedVolumeAxes(axisX, axisY, structureSet); |
1192 Vector3D axisY = Vector3D::CrossProduct(geometry.GetSlicesNormal(), axisX); | 1347 |
1348 Vector3D axisZ = Vector3D::CrossProduct(axisX, axisY); | |
1193 | 1349 |
1194 if (!IsNear(1, axisX.ComputeNorm()) || | 1350 if (!IsNear(1, axisX.ComputeNorm()) || |
1195 !IsNear(1, axisY.ComputeNorm())) | 1351 !IsNear(1, axisY.ComputeNorm()) || |
1352 !Vector3D::AreParallel(axisZ, geometry.GetSlicesNormal())) | |
1196 { | 1353 { |
1197 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | 1354 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); |
1198 } | 1355 } |
1199 | 1356 |
1200 Extent2D extent; | 1357 Extent2D extent; |
1213 memset(volume->GetScalarPointer(), 0, resolution * resolution * depth); | 1370 memset(volume->GetScalarPointer(), 0, resolution * resolution * depth); |
1214 | 1371 |
1215 for (size_t i = 0; i < structureSet.GetPolygonsCount(); i++) | 1372 for (size_t i = 0; i < structureSet.GetPolygonsCount(); i++) |
1216 { | 1373 { |
1217 const StructurePolygon& polygon = structureSet.GetPolygon(i); | 1374 const StructurePolygon& polygon = structureSet.GetPolygon(i); |
1218 if (roiNames.find(polygon.GetRoiName()) == roiNames.end()) | 1375 if (roiNames.find(polygon.GetRoiName()) != roiNames.end()) |
1219 { | 1376 { |
1220 // This polygon doesn't correspond to a ROI of interest | 1377 // This polygon corresponds to a ROI of interest |
1221 continue; | 1378 |
1222 } | 1379 size_t z; |
1223 | 1380 if (geometry.LookupSliceIndex(z, polygon)) |
1224 size_t j; | 1381 { |
1225 if (!geometry.LookupSliceIndex(j, polygon)) | 1382 std::vector<Orthanc::ImageProcessing::ImagePoint> points; |
1226 { | 1383 points.reserve(polygon.GetPointsCount()); |
1227 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | 1384 |
1228 } | 1385 for (size_t j = 0; j < polygon.GetPointsCount(); j++) |
1229 | 1386 { |
1230 std::vector<Orthanc::ImageProcessing::ImagePoint> points; | 1387 const Vector3D& point = polygon.GetPoint(j); |
1231 points.reserve(polygon.GetPointsCount()); | 1388 double x = (Vector3D::DotProduct(point, axisX) - extent.GetMinX()) / extent.GetWidth() * static_cast<double>(resolution); |
1232 for (size_t j = 0; j < polygon.GetPointsCount(); j++) | 1389 double y = (Vector3D::DotProduct(point, axisY) - extent.GetMinY()) / extent.GetHeight() * static_cast<double>(resolution); |
1233 { | 1390 points.push_back(Orthanc::ImageProcessing::ImagePoint(static_cast<int32_t>(std::floor(x)), |
1234 const Vector3D& point = polygon.GetPoint(j); | 1391 static_cast<int32_t>(std::floor(y)))); |
1235 double x = (Vector3D::DotProduct(point, axisX) - extent.GetMinX()) / extent.GetWidth() * static_cast<double>(resolution); | 1392 } |
1236 double y = (Vector3D::DotProduct(point, axisY) - extent.GetMinY()) / extent.GetHeight() * static_cast<double>(resolution); | 1393 |
1237 points.push_back(Orthanc::ImageProcessing::ImagePoint(static_cast<int32_t>(std::floor(x)), | 1394 Orthanc::ImageAccessor slice; |
1238 static_cast<int32_t>(std::floor(y)))); | 1395 slice.AssignWritable(Orthanc::PixelFormat_Grayscale8, resolution, resolution, resolution /* pitch */, |
1239 } | 1396 reinterpret_cast<uint8_t*>(volume->GetScalarPointer()) + z * resolution * resolution); |
1240 | 1397 |
1241 Orthanc::ImageAccessor slice; | 1398 XorFiller filler(slice); |
1242 slice.AssignWritable(Orthanc::PixelFormat_Grayscale8, resolution, resolution, resolution /* pitch */, | 1399 Orthanc::ImageProcessing::FillPolygon(filler, points); |
1243 reinterpret_cast<uint8_t*>(volume->GetScalarPointer()) + j * resolution * resolution); | 1400 } |
1244 | 1401 } |
1245 XorFiller filler(slice); | 1402 } |
1246 Orthanc::ImageProcessing::FillPolygon(filler, points); | 1403 |
1247 } | 1404 volume->SetSpacing(extent.GetWidth() / static_cast<double>(resolution), |
1248 | 1405 extent.GetHeight() / static_cast<double>(resolution), |
1249 volume->SetSpacing( | 1406 geometry.GetSlicesSpacing()); |
1250 extent.GetWidth() / static_cast<double>(resolution), | |
1251 extent.GetHeight() / static_cast<double>(resolution), | |
1252 (geometry.GetMaxProjectionAlongNormal() - geometry.GetMinProjectionAlongNormal()) / static_cast<double>(depth)); | |
1253 | 1407 |
1254 // TODO | 1408 // TODO |
1255 // volume->SetOrigin() | 1409 // volume->SetOrigin() |
1256 | 1410 |
1257 return EncodeVolume(stl, volume.Get(), resolution, smooth); | 1411 return EncodeVolume(stl, volume.Get(), resolution, smooth); |
1258 } | |
1259 | |
1260 | |
1261 static Orthanc::ParsedDicomFile* LoadInstance(const std::string& instanceId) | |
1262 { | |
1263 std::string dicom; | |
1264 | |
1265 if (!OrthancPlugins::RestApiGetString(dicom, "/instances/" + instanceId + "/file", false)) | |
1266 { | |
1267 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
1268 } | |
1269 else | |
1270 { | |
1271 return new Orthanc::ParsedDicomFile(dicom); | |
1272 } | |
1273 } | 1412 } |
1274 | 1413 |
1275 | 1414 |
1276 void ListStructures(OrthancPluginRestOutput* output, | 1415 void ListStructures(OrthancPluginRestOutput* output, |
1277 const char* url, | 1416 const char* url, |
1510 std::unique_ptr<Orthanc::ParsedDicomFile> dicom(LoadInstance(instanceId)); | 1649 std::unique_ptr<Orthanc::ParsedDicomFile> dicom(LoadInstance(instanceId)); |
1511 DcmDataset& dataset = *dicom->GetDcmtkObject().getDataset(); | 1650 DcmDataset& dataset = *dicom->GetDcmtkObject().getDataset(); |
1512 | 1651 |
1513 std::string stl; | 1652 std::string stl; |
1514 if (GetStringValue(dataset, DCM_MIMETypeOfEncapsulatedDocument) != Orthanc::MIME_STL || | 1653 if (GetStringValue(dataset, DCM_MIMETypeOfEncapsulatedDocument) != Orthanc::MIME_STL || |
1515 GetStringValue(dataset, DCM_SOPClassUID) != STL_SOP_CLASS_UID || | 1654 GetStringValue(dataset, DCM_SOPClassUID) != UID_EncapsulatedSTLStorage || |
1516 !dicom->GetTagValue(stl, Orthanc::DICOM_TAG_ENCAPSULATED_DOCUMENT)) | 1655 !dicom->GetTagValue(stl, Orthanc::DICOM_TAG_ENCAPSULATED_DOCUMENT)) |
1517 { | 1656 { |
1518 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "DICOM instance not encapsulating a STL model: " + instanceId); | 1657 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "DICOM instance not encapsulating a STL model: " + instanceId); |
1519 } | 1658 } |
1520 else | 1659 else |