comparison OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 4860:3e9a76464e8a openssl-3.x

integration mainline->openssl-3.x
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 24 Dec 2021 16:52:51 +0100
parents 2e71a08eea15 8b51d65584f0
children 6eff25f70121
comparison
equal deleted inserted replaced
4832:2e71a08eea15 4860:3e9a76464e8a
29 #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" 29 #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
30 #include "../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h" 30 #include "../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h"
31 #include "../../../OrthancFramework/Sources/HttpServer/HttpContentNegociation.h" 31 #include "../../../OrthancFramework/Sources/HttpServer/HttpContentNegociation.h"
32 #include "../../../OrthancFramework/Sources/Images/Image.h" 32 #include "../../../OrthancFramework/Sources/Images/Image.h"
33 #include "../../../OrthancFramework/Sources/Images/ImageProcessing.h" 33 #include "../../../OrthancFramework/Sources/Images/ImageProcessing.h"
34 #include "../../../OrthancFramework/Sources/Images/NumpyWriter.h"
34 #include "../../../OrthancFramework/Sources/Logging.h" 35 #include "../../../OrthancFramework/Sources/Logging.h"
35 #include "../../../OrthancFramework/Sources/MultiThreading/Semaphore.h" 36 #include "../../../OrthancFramework/Sources/MultiThreading/Semaphore.h"
36 #include "../../../OrthancFramework/Sources/SerializationToolbox.h" 37 #include "../../../OrthancFramework/Sources/SerializationToolbox.h"
37 38
38 #include "../OrthancConfiguration.h" 39 #include "../OrthancConfiguration.h"
1067 if (windowWidth <= 1.0f) 1068 if (windowWidth <= 1.0f)
1068 { 1069 {
1069 windowWidth = 1; 1070 windowWidth = 1;
1070 } 1071 }
1071 1072
1072 if (std::abs(rescaleSlope) <= 0.1) 1073 if (std::abs(rescaleSlope) <= 0.0001)
1073 { 1074 {
1074 rescaleSlope = 0.1; 1075 rescaleSlope = 0.0001;
1075 } 1076 }
1076 1077
1077 const double scaling = 255.0 * rescaleSlope / windowWidth; 1078 const double scaling = 255.0 * rescaleSlope / windowWidth;
1078 const double offset = (rescaleIntercept - windowCenter + windowWidth / 2.0) / rescaleSlope; 1079 const double offset = (rescaleIntercept - windowCenter + windowWidth / 2.0) / rescaleSlope;
1079 1080
1125 { 1126 {
1126 Semaphore::Locker locker(throttlingSemaphore_); 1127 Semaphore::Locker locker(throttlingSemaphore_);
1127 1128
1128 RenderedFrameHandler handler; 1129 RenderedFrameHandler handler;
1129 IDecodedFrameHandler::Apply(call, handler, ImageExtractionMode_Preview /* arbitrary value */, true); 1130 IDecodedFrameHandler::Apply(call, handler, ImageExtractionMode_Preview /* arbitrary value */, true);
1131 }
1132
1133
1134 static void DocumentSharedNumpy(RestApiGetCall& call)
1135 {
1136 call.GetDocumentation()
1137 .SetUriArgument("id", "Orthanc identifier of the DICOM resource of interest")
1138 .SetHttpGetArgument("compress", RestApiCallDocumentation::Type_Boolean, "Compress the file as `.npz`", false)
1139 .SetHttpGetArgument("rescale", RestApiCallDocumentation::Type_Boolean,
1140 "On grayscale images, apply the rescaling and return floating-point values", false)
1141 .AddAnswerType(MimeType_PlainText, "Numpy file: https://numpy.org/devdocs/reference/generated/numpy.lib.format.html");
1142 }
1143
1144
1145 namespace
1146 {
1147 class NumpyVisitor : public boost::noncopyable
1148 {
1149 private:
1150 bool rescale_;
1151 unsigned int depth_;
1152 unsigned int currentDepth_;
1153 unsigned int width_;
1154 unsigned int height_;
1155 PixelFormat format_;
1156 ChunkedBuffer buffer_;
1157
1158 public:
1159 NumpyVisitor(unsigned int depth /* can be zero if 2D frame */,
1160 bool rescale) :
1161 rescale_(rescale),
1162 depth_(depth),
1163 currentDepth_(0),
1164 width_(0), // dummy initialization
1165 height_(0), // dummy initialization
1166 format_(PixelFormat_Grayscale8) // dummy initialization
1167 {
1168 }
1169
1170 void WriteFrame(const ParsedDicomFile& dicom,
1171 unsigned int frame)
1172 {
1173 std::unique_ptr<ImageAccessor> decoded(dicom.DecodeFrame(frame));
1174
1175 if (decoded.get() == NULL)
1176 {
1177 throw OrthancException(ErrorCode_NotImplemented, "Cannot decode DICOM instance");
1178 }
1179
1180 if (currentDepth_ == 0)
1181 {
1182 width_ = decoded->GetWidth();
1183 height_ = decoded->GetHeight();
1184 format_ = decoded->GetFormat();
1185 }
1186 else if (width_ != decoded->GetWidth() ||
1187 height_ != decoded->GetHeight())
1188 {
1189 throw OrthancException(ErrorCode_IncompatibleImageSize, "The size of the frames varies across the instance(s)");
1190 }
1191 else if (format_ != decoded->GetFormat())
1192 {
1193 throw OrthancException(ErrorCode_IncompatibleImageFormat, "The pixel format of the frames varies across the instance(s)");
1194 }
1195
1196 if (rescale_ &&
1197 decoded->GetFormat() != PixelFormat_RGB24)
1198 {
1199 if (currentDepth_ == 0)
1200 {
1201 NumpyWriter::WriteHeader(buffer_, depth_, width_, height_, PixelFormat_Float32);
1202 }
1203
1204 double rescaleIntercept, rescaleSlope;
1205 dicom.GetRescale(rescaleIntercept, rescaleSlope, frame);
1206
1207 Image converted(PixelFormat_Float32, decoded->GetWidth(), decoded->GetHeight(), false);
1208 ImageProcessing::Convert(converted, *decoded);
1209 ImageProcessing::ShiftScale2(converted, static_cast<float>(rescaleIntercept), static_cast<float>(rescaleSlope), false);
1210
1211 NumpyWriter::WritePixels(buffer_, converted);
1212 }
1213 else
1214 {
1215 if (currentDepth_ == 0)
1216 {
1217 NumpyWriter::WriteHeader(buffer_, depth_, width_, height_, format_);
1218 }
1219
1220 NumpyWriter::WritePixels(buffer_, *decoded);
1221 }
1222
1223 currentDepth_ ++;
1224 }
1225
1226 void Answer(RestApiOutput& output,
1227 bool compress)
1228 {
1229 if ((depth_ == 0 && currentDepth_ != 1) ||
1230 (depth_ != 0 && currentDepth_ != depth_))
1231 {
1232 throw OrthancException(ErrorCode_BadSequenceOfCalls);
1233 }
1234 else
1235 {
1236 std::string answer;
1237 NumpyWriter::Finalize(answer, buffer_, compress);
1238 output.AnswerBuffer(answer, MimeType_Binary);
1239 }
1240 }
1241 };
1242 }
1243
1244
1245 static void GetNumpyFrame(RestApiGetCall& call)
1246 {
1247 if (call.IsDocumentation())
1248 {
1249 DocumentSharedNumpy(call);
1250 call.GetDocumentation()
1251 .SetTag("Instances")
1252 .SetSummary("Decode frame for numpy")
1253 .SetDescription("Decode one frame of interest from the given DICOM instance, for use with numpy in Python. "
1254 "The numpy array has 3 dimensions: (height, width, color channel).")
1255 .SetUriArgument("frame", RestApiCallDocumentation::Type_Number, "Index of the frame (starts at `0`)");
1256 }
1257 else
1258 {
1259 const std::string instanceId = call.GetUriComponent("id", "");
1260 const bool compress = call.GetBooleanArgument("compress", false);
1261 const bool rescale = call.GetBooleanArgument("rescale", true);
1262
1263 uint32_t frame;
1264 if (!SerializationToolbox::ParseUnsignedInteger32(frame, call.GetUriComponent("frame", "0")))
1265 {
1266 throw OrthancException(ErrorCode_ParameterOutOfRange, "Expected an unsigned integer for the \"frame\" argument");
1267 }
1268
1269 NumpyVisitor visitor(0 /* no depth, 2D frame */, rescale);
1270
1271 {
1272 Semaphore::Locker throttling(throttlingSemaphore_);
1273 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), instanceId);
1274
1275 visitor.WriteFrame(locker.GetDicom(), frame);
1276 }
1277
1278 visitor.Answer(call.GetOutput(), compress);
1279 }
1280 }
1281
1282
1283 static void GetNumpyInstance(RestApiGetCall& call)
1284 {
1285 if (call.IsDocumentation())
1286 {
1287 DocumentSharedNumpy(call);
1288 call.GetDocumentation()
1289 .SetTag("Instances")
1290 .SetSummary("Decode instance for numpy")
1291 .SetDescription("Decode the given DICOM instance, for use with numpy in Python. "
1292 "The numpy array has 4 dimensions: (frame, height, width, color channel).");
1293 }
1294 else
1295 {
1296 const std::string instanceId = call.GetUriComponent("id", "");
1297 const bool compress = call.GetBooleanArgument("compress", false);
1298 const bool rescale = call.GetBooleanArgument("rescale", true);
1299
1300 {
1301 Semaphore::Locker throttling(throttlingSemaphore_);
1302 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), instanceId);
1303
1304 const unsigned int depth = locker.GetDicom().GetFramesCount();
1305 if (depth == 0)
1306 {
1307 throw OrthancException(ErrorCode_BadFileFormat, "Empty DICOM instance");
1308 }
1309
1310 NumpyVisitor visitor(depth, rescale);
1311
1312 for (unsigned int frame = 0; frame < depth; frame++)
1313 {
1314 visitor.WriteFrame(locker.GetDicom(), frame);
1315 }
1316
1317 visitor.Answer(call.GetOutput(), compress);
1318 }
1319 }
1320 }
1321
1322
1323 static void GetNumpySeries(RestApiGetCall& call)
1324 {
1325 if (call.IsDocumentation())
1326 {
1327 DocumentSharedNumpy(call);
1328 call.GetDocumentation()
1329 .SetTag("Series")
1330 .SetSummary("Decode series for numpy")
1331 .SetDescription("Decode the given DICOM series, for use with numpy in Python. "
1332 "The numpy array has 4 dimensions: (frame, height, width, color channel).");
1333 }
1334 else
1335 {
1336 const std::string seriesId = call.GetUriComponent("id", "");
1337 const bool compress = call.GetBooleanArgument("compress", false);
1338 const bool rescale = call.GetBooleanArgument("rescale", true);
1339
1340 Semaphore::Locker throttling(throttlingSemaphore_);
1341
1342 ServerIndex& index = OrthancRestApi::GetIndex(call);
1343 SliceOrdering ordering(index, seriesId);
1344
1345 unsigned int depth = 0;
1346 for (size_t i = 0; i < ordering.GetInstancesCount(); i++)
1347 {
1348 depth += ordering.GetFramesCount(i);
1349 }
1350
1351 ServerContext& context = OrthancRestApi::GetContext(call);
1352
1353 NumpyVisitor visitor(depth, rescale);
1354
1355 for (size_t i = 0; i < ordering.GetInstancesCount(); i++)
1356 {
1357 const std::string& instanceId = ordering.GetInstanceId(i);
1358 unsigned int framesCount = ordering.GetFramesCount(i);
1359
1360 {
1361 ServerContext::DicomCacheLocker locker(context, instanceId);
1362
1363 for (unsigned int frame = 0; frame < framesCount; frame++)
1364 {
1365 visitor.WriteFrame(locker.GetDicom(), frame);
1366 }
1367 }
1368 }
1369
1370 visitor.Answer(call.GetOutput(), compress);
1371 }
1130 } 1372 }
1131 1373
1132 1374
1133 static void GetMatlabImage(RestApiGetCall& call) 1375 static void GetMatlabImage(RestApiGetCall& call)
1134 { 1376 {
1675 call.GetDocumentation() 1917 call.GetDocumentation()
1676 .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */)) 1918 .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
1677 .SetSummary("List attachments") 1919 .SetSummary("List attachments")
1678 .SetDescription("Get the list of attachments that are associated with the given " + r) 1920 .SetDescription("Get the list of attachments that are associated with the given " + r)
1679 .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") 1921 .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
1922 .SetHttpGetArgument("full", RestApiCallDocumentation::Type_String,
1923 "If present, retrieve the attachments list and their numerical ids", false)
1680 .AddAnswerType(MimeType_Json, "JSON array containing the names of the attachments") 1924 .AddAnswerType(MimeType_Json, "JSON array containing the names of the attachments")
1681 .SetHttpGetSample(GetDocumentationSampleResource(t) + "/attachments", true); 1925 .SetHttpGetSample(GetDocumentationSampleResource(t) + "/attachments", true);
1682 return; 1926 return;
1683 } 1927 }
1684 1928
1685 const std::string resourceType = call.GetFullUri() [0]; 1929 const std::string resourceType = call.GetFullUri() [0];
1686 const std::string publicId = call.GetUriComponent("id", ""); 1930 const std::string publicId = call.GetUriComponent("id", "");
1687 std::set<FileContentType> attachments; 1931 std::set<FileContentType> attachments;
1688 OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str())); 1932 OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str()));
1689 1933
1690 Json::Value result = Json::arrayValue; 1934 Json::Value result;
1691 1935
1692 for (std::set<FileContentType>::const_iterator 1936 if (call.HasArgument("full"))
1693 it = attachments.begin(); it != attachments.end(); ++it) 1937 {
1694 { 1938 result = Json::objectValue;
1695 result.append(EnumerationToString(*it)); 1939
1940 for (std::set<FileContentType>::const_iterator
1941 it = attachments.begin(); it != attachments.end(); ++it)
1942 {
1943 std::string key = EnumerationToString(*it);
1944 result[key] = static_cast<uint16_t>(*it);
1945 }
1946 }
1947 else
1948 {
1949 result = Json::arrayValue;
1950
1951 for (std::set<FileContentType>::const_iterator
1952 it = attachments.begin(); it != attachments.end(); ++it)
1953 {
1954 result.append(EnumerationToString(*it));
1955 }
1696 } 1956 }
1697 1957
1698 call.GetOutput().AnswerJson(result); 1958 call.GetOutput().AnswerJson(result);
1699 } 1959 }
1700 1960
3382 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); 3642 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
3383 Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); 3643 Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>);
3384 Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); 3644 Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage);
3385 Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>); 3645 Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>);
3386 Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>); 3646 Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>);
3647 Register("/instances/{id}/frames/{frame}/numpy", GetNumpyFrame); // New in Orthanc 1.9.8
3387 Register("/instances/{id}/pdf", ExtractPdf); 3648 Register("/instances/{id}/pdf", ExtractPdf);
3388 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); 3649 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>);
3389 Register("/instances/{id}/rendered", GetRenderedFrame); 3650 Register("/instances/{id}/rendered", GetRenderedFrame);
3390 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); 3651 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
3391 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); 3652 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
3392 Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); 3653 Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>);
3393 Register("/instances/{id}/matlab", GetMatlabImage); 3654 Register("/instances/{id}/matlab", GetMatlabImage);
3394 Register("/instances/{id}/header", GetInstanceHeader); 3655 Register("/instances/{id}/header", GetInstanceHeader);
3656 Register("/instances/{id}/numpy", GetNumpyInstance); // New in Orthanc 1.9.8
3395 3657
3396 Register("/patients/{id}/protected", IsProtectedPatient); 3658 Register("/patients/{id}/protected", IsProtectedPatient);
3397 Register("/patients/{id}/protected", SetPatientProtection); 3659 Register("/patients/{id}/protected", SetPatientProtection);
3398 3660
3399 std::vector<std::string> resourceTypes; 3661 std::vector<std::string> resourceTypes;
3448 Register("/series/{id}/instances-tags", GetChildInstancesTags); 3710 Register("/series/{id}/instances-tags", GetChildInstancesTags);
3449 3711
3450 Register("/instances/{id}/content/*", GetRawContent); 3712 Register("/instances/{id}/content/*", GetRawContent);
3451 3713
3452 Register("/series/{id}/ordered-slices", OrderSlices); 3714 Register("/series/{id}/ordered-slices", OrderSlices);
3715 Register("/series/{id}/numpy", GetNumpySeries); // New in Orthanc 1.9.8
3453 3716
3454 Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>); 3717 Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>);
3455 Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>); 3718 Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>);
3456 Register("/series/{id}/reconstruct", ReconstructResource<ResourceType_Series>); 3719 Register("/series/{id}/reconstruct", ReconstructResource<ResourceType_Series>);
3457 Register("/instances/{id}/reconstruct", ReconstructResource<ResourceType_Instance>); 3720 Register("/instances/{id}/reconstruct", ReconstructResource<ResourceType_Instance>);