Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 4834:bec432ee1094
download of numpy arrays from the REST API
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 26 Nov 2021 19:03:32 +0100 |
parents | 7053502fbf97 |
children | ec1e9571b645 |
comparison
equal
deleted
inserted
replaced
4833:970092a67897 | 4834:bec432ee1094 |
---|---|
41 #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" | 41 #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" |
42 #include "../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h" | 42 #include "../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h" |
43 #include "../../../OrthancFramework/Sources/HttpServer/HttpContentNegociation.h" | 43 #include "../../../OrthancFramework/Sources/HttpServer/HttpContentNegociation.h" |
44 #include "../../../OrthancFramework/Sources/Images/Image.h" | 44 #include "../../../OrthancFramework/Sources/Images/Image.h" |
45 #include "../../../OrthancFramework/Sources/Images/ImageProcessing.h" | 45 #include "../../../OrthancFramework/Sources/Images/ImageProcessing.h" |
46 #include "../../../OrthancFramework/Sources/Images/NumpyWriter.h" | |
46 #include "../../../OrthancFramework/Sources/Logging.h" | 47 #include "../../../OrthancFramework/Sources/Logging.h" |
47 #include "../../../OrthancFramework/Sources/MultiThreading/Semaphore.h" | 48 #include "../../../OrthancFramework/Sources/MultiThreading/Semaphore.h" |
48 #include "../../../OrthancFramework/Sources/SerializationToolbox.h" | 49 #include "../../../OrthancFramework/Sources/SerializationToolbox.h" |
49 | 50 |
50 #include "../OrthancConfiguration.h" | 51 #include "../OrthancConfiguration.h" |
1079 if (windowWidth <= 1.0f) | 1080 if (windowWidth <= 1.0f) |
1080 { | 1081 { |
1081 windowWidth = 1; | 1082 windowWidth = 1; |
1082 } | 1083 } |
1083 | 1084 |
1084 if (std::abs(rescaleSlope) <= 0.1) | 1085 if (std::abs(rescaleSlope) <= 0.0001) |
1085 { | 1086 { |
1086 rescaleSlope = 0.1; | 1087 rescaleSlope = 0.0001; |
1087 } | 1088 } |
1088 | 1089 |
1089 const double scaling = 255.0 * rescaleSlope / windowWidth; | 1090 const double scaling = 255.0 * rescaleSlope / windowWidth; |
1090 const double offset = (rescaleIntercept - windowCenter + windowWidth / 2.0) / rescaleSlope; | 1091 const double offset = (rescaleIntercept - windowCenter + windowWidth / 2.0) / rescaleSlope; |
1091 | 1092 |
1137 { | 1138 { |
1138 Semaphore::Locker locker(throttlingSemaphore_); | 1139 Semaphore::Locker locker(throttlingSemaphore_); |
1139 | 1140 |
1140 RenderedFrameHandler handler; | 1141 RenderedFrameHandler handler; |
1141 IDecodedFrameHandler::Apply(call, handler, ImageExtractionMode_Preview /* arbitrary value */, true); | 1142 IDecodedFrameHandler::Apply(call, handler, ImageExtractionMode_Preview /* arbitrary value */, true); |
1143 } | |
1144 | |
1145 | |
1146 static void DocumentSharedNumpy(RestApiGetCall& call) | |
1147 { | |
1148 call.GetDocumentation() | |
1149 .SetUriArgument("id", "Orthanc identifier of the DICOM resource of interest") | |
1150 .SetHttpGetArgument("compress", RestApiCallDocumentation::Type_Boolean, "Compress the file as `.npz`", false) | |
1151 .SetHttpGetArgument("rescale", RestApiCallDocumentation::Type_Boolean, | |
1152 "On grayscale images, apply the rescaling and return floating-point values", false) | |
1153 .AddAnswerType(MimeType_PlainText, "Numpy file: https://numpy.org/devdocs/reference/generated/numpy.lib.format.html"); | |
1154 } | |
1155 | |
1156 | |
1157 namespace | |
1158 { | |
1159 class NumpyVisitor : public boost::noncopyable | |
1160 { | |
1161 private: | |
1162 bool rescale_; | |
1163 unsigned int depth_; | |
1164 unsigned int currentDepth_; | |
1165 unsigned int height_; | |
1166 unsigned int width_; | |
1167 PixelFormat format_; | |
1168 ChunkedBuffer buffer_; | |
1169 | |
1170 public: | |
1171 NumpyVisitor(unsigned int depth /* can be zero if 2D frame */, | |
1172 bool rescale) : | |
1173 rescale_(rescale), | |
1174 depth_(depth), | |
1175 currentDepth_(0) | |
1176 { | |
1177 } | |
1178 | |
1179 void WriteFrame(ParsedDicomFile& dicom, | |
1180 unsigned int frame) | |
1181 { | |
1182 std::unique_ptr<ImageAccessor> decoded(dicom.DecodeFrame(frame)); | |
1183 | |
1184 if (decoded.get() == NULL) | |
1185 { | |
1186 throw OrthancException(ErrorCode_NotImplemented, "Cannot decode DICOM instance"); | |
1187 } | |
1188 | |
1189 if (currentDepth_ == 0) | |
1190 { | |
1191 width_ = decoded->GetWidth(); | |
1192 height_ = decoded->GetHeight(); | |
1193 format_ = decoded->GetFormat(); | |
1194 } | |
1195 else if (width_ != decoded->GetWidth() || | |
1196 height_ != decoded->GetHeight()) | |
1197 { | |
1198 throw OrthancException(ErrorCode_IncompatibleImageSize, "The size of the frames varies across the instance(s)"); | |
1199 } | |
1200 else if (format_ != decoded->GetFormat()) | |
1201 { | |
1202 throw OrthancException(ErrorCode_IncompatibleImageFormat, "The pixel format of the frames varies across the instance(s)"); | |
1203 } | |
1204 | |
1205 if (rescale_ && | |
1206 decoded->GetFormat() != PixelFormat_RGB24) | |
1207 { | |
1208 if (currentDepth_ == 0) | |
1209 { | |
1210 NumpyWriter::WriteHeader(buffer_, depth_, width_, height_, PixelFormat_Float32); | |
1211 } | |
1212 | |
1213 double rescaleIntercept, rescaleSlope; | |
1214 dicom.GetRescale(rescaleIntercept, rescaleSlope, frame); | |
1215 | |
1216 Image converted(PixelFormat_Float32, decoded->GetWidth(), decoded->GetHeight(), false); | |
1217 ImageProcessing::Convert(converted, *decoded); | |
1218 ImageProcessing::ShiftScale2(converted, static_cast<float>(rescaleIntercept), static_cast<float>(rescaleSlope), false); | |
1219 | |
1220 NumpyWriter::WritePixels(buffer_, converted); | |
1221 } | |
1222 else | |
1223 { | |
1224 if (currentDepth_ == 0) | |
1225 { | |
1226 NumpyWriter::WriteHeader(buffer_, depth_, width_, height_, format_); | |
1227 } | |
1228 | |
1229 NumpyWriter::WritePixels(buffer_, *decoded); | |
1230 } | |
1231 | |
1232 currentDepth_ ++; | |
1233 } | |
1234 | |
1235 void Answer(RestApiOutput& output, | |
1236 bool compress) | |
1237 { | |
1238 if ((depth_ == 0 && currentDepth_ != 1) || | |
1239 (depth_ != 0 && currentDepth_ != depth_)) | |
1240 { | |
1241 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
1242 } | |
1243 else | |
1244 { | |
1245 std::string answer; | |
1246 NumpyWriter::Finalize(answer, buffer_, compress); | |
1247 output.AnswerBuffer(answer, MimeType_Binary); | |
1248 } | |
1249 } | |
1250 }; | |
1251 } | |
1252 | |
1253 | |
1254 static void GetNumpyFrame(RestApiGetCall& call) | |
1255 { | |
1256 if (call.IsDocumentation()) | |
1257 { | |
1258 DocumentSharedNumpy(call); | |
1259 call.GetDocumentation() | |
1260 .SetTag("Instances") | |
1261 .SetSummary("Decode frame for numpy") | |
1262 .SetDescription("Decode one frame of interest from the given DICOM instance, for use with numpy in Python") | |
1263 .SetUriArgument("frame", RestApiCallDocumentation::Type_Number, "Index of the frame (starts at `0`)"); | |
1264 } | |
1265 else | |
1266 { | |
1267 const std::string instanceId = call.GetUriComponent("id", ""); | |
1268 const bool compress = call.GetBooleanArgument("compress", false); | |
1269 const bool rescale = call.GetBooleanArgument("rescale", true); | |
1270 | |
1271 uint32_t frame; | |
1272 if (!SerializationToolbox::ParseUnsignedInteger32(frame, call.GetUriComponent("frame", "0"))) | |
1273 { | |
1274 throw OrthancException(ErrorCode_ParameterOutOfRange, "Expected an unsigned integer for the \"frame\" argument"); | |
1275 } | |
1276 | |
1277 NumpyVisitor visitor(0 /* no depth, 2D frame */, rescale); | |
1278 | |
1279 { | |
1280 Semaphore::Locker throttling(throttlingSemaphore_); | |
1281 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), instanceId); | |
1282 | |
1283 visitor.WriteFrame(locker.GetDicom(), frame); | |
1284 } | |
1285 | |
1286 visitor.Answer(call.GetOutput(), compress); | |
1287 } | |
1288 } | |
1289 | |
1290 | |
1291 static void GetNumpyInstance(RestApiGetCall& call) | |
1292 { | |
1293 if (call.IsDocumentation()) | |
1294 { | |
1295 DocumentSharedNumpy(call); | |
1296 call.GetDocumentation() | |
1297 .SetTag("Instances") | |
1298 .SetSummary("Decode instance for numpy") | |
1299 .SetDescription("Decode the given DICOM instance, for use with numpy in Python"); | |
1300 } | |
1301 else | |
1302 { | |
1303 const std::string instanceId = call.GetUriComponent("id", ""); | |
1304 const bool compress = call.GetBooleanArgument("compress", false); | |
1305 const bool rescale = call.GetBooleanArgument("rescale", true); | |
1306 | |
1307 { | |
1308 Semaphore::Locker throttling(throttlingSemaphore_); | |
1309 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), instanceId); | |
1310 | |
1311 const unsigned int depth = locker.GetDicom().GetFramesCount(); | |
1312 if (depth == 0) | |
1313 { | |
1314 throw OrthancException(ErrorCode_BadFileFormat, "Empty DICOM instance"); | |
1315 } | |
1316 | |
1317 NumpyVisitor visitor(depth, rescale); | |
1318 | |
1319 for (unsigned int frame = 0; frame < depth; frame++) | |
1320 { | |
1321 visitor.WriteFrame(locker.GetDicom(), frame); | |
1322 } | |
1323 | |
1324 visitor.Answer(call.GetOutput(), compress); | |
1325 } | |
1326 } | |
1327 } | |
1328 | |
1329 | |
1330 static void GetNumpySeries(RestApiGetCall& call) | |
1331 { | |
1332 if (call.IsDocumentation()) | |
1333 { | |
1334 DocumentSharedNumpy(call); | |
1335 call.GetDocumentation() | |
1336 .SetTag("Series") | |
1337 .SetSummary("Decode series for numpy") | |
1338 .SetDescription("Decode the given DICOM series, for use with numpy in Python"); | |
1339 } | |
1340 else | |
1341 { | |
1342 const std::string seriesId = call.GetUriComponent("id", ""); | |
1343 const bool compress = call.GetBooleanArgument("compress", false); | |
1344 const bool rescale = call.GetBooleanArgument("rescale", true); | |
1345 | |
1346 Semaphore::Locker throttling(throttlingSemaphore_); | |
1347 | |
1348 ServerIndex& index = OrthancRestApi::GetIndex(call); | |
1349 SliceOrdering ordering(index, seriesId); | |
1350 | |
1351 unsigned int depth = 0; | |
1352 for (size_t i = 0; i < ordering.GetInstancesCount(); i++) | |
1353 { | |
1354 depth += ordering.GetFramesCount(i); | |
1355 } | |
1356 | |
1357 ServerContext& context = OrthancRestApi::GetContext(call); | |
1358 | |
1359 NumpyVisitor visitor(depth, rescale); | |
1360 | |
1361 for (size_t i = 0; i < ordering.GetInstancesCount(); i++) | |
1362 { | |
1363 const std::string& instanceId = ordering.GetInstanceId(i); | |
1364 unsigned int framesCount = ordering.GetFramesCount(i); | |
1365 | |
1366 { | |
1367 ServerContext::DicomCacheLocker locker(context, instanceId); | |
1368 | |
1369 for (unsigned int frame = 0; frame < framesCount; frame++) | |
1370 { | |
1371 visitor.WriteFrame(locker.GetDicom(), frame); | |
1372 } | |
1373 } | |
1374 } | |
1375 | |
1376 visitor.Answer(call.GetOutput(), compress); | |
1377 } | |
1142 } | 1378 } |
1143 | 1379 |
1144 | 1380 |
1145 static void GetMatlabImage(RestApiGetCall& call) | 1381 static void GetMatlabImage(RestApiGetCall& call) |
1146 { | 1382 { |
3394 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | 3630 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); |
3395 Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); | 3631 Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); |
3396 Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); | 3632 Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); |
3397 Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>); | 3633 Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>); |
3398 Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>); | 3634 Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>); |
3635 Register("/instances/{id}/frames/{frame}/numpy", GetNumpyFrame); // New in Orthanc 1.9.8 | |
3399 Register("/instances/{id}/pdf", ExtractPdf); | 3636 Register("/instances/{id}/pdf", ExtractPdf); |
3400 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); | 3637 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); |
3401 Register("/instances/{id}/rendered", GetRenderedFrame); | 3638 Register("/instances/{id}/rendered", GetRenderedFrame); |
3402 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); | 3639 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); |
3403 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | 3640 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); |
3404 Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); | 3641 Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); |
3405 Register("/instances/{id}/matlab", GetMatlabImage); | 3642 Register("/instances/{id}/matlab", GetMatlabImage); |
3406 Register("/instances/{id}/header", GetInstanceHeader); | 3643 Register("/instances/{id}/header", GetInstanceHeader); |
3644 Register("/instances/{id}/numpy", GetNumpyInstance); // New in Orthanc 1.9.8 | |
3407 | 3645 |
3408 Register("/patients/{id}/protected", IsProtectedPatient); | 3646 Register("/patients/{id}/protected", IsProtectedPatient); |
3409 Register("/patients/{id}/protected", SetPatientProtection); | 3647 Register("/patients/{id}/protected", SetPatientProtection); |
3410 | 3648 |
3411 std::vector<std::string> resourceTypes; | 3649 std::vector<std::string> resourceTypes; |
3460 Register("/series/{id}/instances-tags", GetChildInstancesTags); | 3698 Register("/series/{id}/instances-tags", GetChildInstancesTags); |
3461 | 3699 |
3462 Register("/instances/{id}/content/*", GetRawContent); | 3700 Register("/instances/{id}/content/*", GetRawContent); |
3463 | 3701 |
3464 Register("/series/{id}/ordered-slices", OrderSlices); | 3702 Register("/series/{id}/ordered-slices", OrderSlices); |
3703 Register("/series/{id}/numpy", GetNumpySeries); // New in Orthanc 1.9.8 | |
3465 | 3704 |
3466 Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>); | 3705 Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>); |
3467 Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>); | 3706 Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>); |
3468 Register("/series/{id}/reconstruct", ReconstructResource<ResourceType_Series>); | 3707 Register("/series/{id}/reconstruct", ReconstructResource<ResourceType_Series>); |
3469 Register("/instances/{id}/reconstruct", ReconstructResource<ResourceType_Instance>); | 3708 Register("/instances/{id}/reconstruct", ReconstructResource<ResourceType_Instance>); |