Mercurial > hg > orthanc
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>); |