comparison OrthancServer/OrthancRestApi/OrthancRestModalities.cpp @ 3761:3b5feb2bbd4b transcoding

integration mainline->transcoding
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 16 Mar 2020 12:17:43 +0100
parents e69c556f1913
children 7f083dfae62b
comparison
equal deleted inserted replaced
3748:ca36e3f1112c 3761:3b5feb2bbd4b
44 #include "../ServerContext.h" 44 #include "../ServerContext.h"
45 #include "../ServerJobs/DicomModalityStoreJob.h" 45 #include "../ServerJobs/DicomModalityStoreJob.h"
46 #include "../ServerJobs/DicomMoveScuJob.h" 46 #include "../ServerJobs/DicomMoveScuJob.h"
47 #include "../ServerJobs/OrthancPeerStoreJob.h" 47 #include "../ServerJobs/OrthancPeerStoreJob.h"
48 #include "../ServerToolbox.h" 48 #include "../ServerToolbox.h"
49 #include "../StorageCommitmentReports.h"
49 50
50 51
51 namespace Orthanc 52 namespace Orthanc
52 { 53 {
53 static const char* const KEY_LEVEL = "Level"; 54 static const char* const KEY_LEVEL = "Level";
961 if (moveOriginatorID != 0) 962 if (moveOriginatorID != 0)
962 { 963 {
963 job->SetMoveOriginator(moveOriginatorAET, moveOriginatorID); 964 job->SetMoveOriginator(moveOriginatorAET, moveOriginatorID);
964 } 965 }
965 966
967 // New in Orthanc 1.6.0
968 if (Toolbox::GetJsonBooleanField(request, "StorageCommitment", false))
969 {
970 job->EnableStorageCommitment(true);
971 }
972
966 OrthancRestApi::GetApi(call).SubmitCommandsJob 973 OrthancRestApi::GetApi(call).SubmitCommandsJob
967 (call, job.release(), true /* synchronous by default */, request); 974 (call, job.release(), true /* synchronous by default */, request);
968 } 975 }
969 976
970 977
1271 1278
1272 Json::Value json; 1279 Json::Value json;
1273 if (call.ParseJsonRequest(json)) 1280 if (call.ParseJsonRequest(json))
1274 { 1281 {
1275 const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); 1282 const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
1276 RemoteModalityParameters remote = 1283 const RemoteModalityParameters remote =
1277 MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); 1284 MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
1278 1285
1279 std::unique_ptr<ParsedDicomFile> query 1286 std::unique_ptr<ParsedDicomFile> query
1280 (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0), 1287 (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0),
1281 "" /* no private creator */)); 1288 "" /* no private creator */));
1296 { 1303 {
1297 throw OrthancException(ErrorCode_BadFileFormat, "Must provide a JSON object"); 1304 throw OrthancException(ErrorCode_BadFileFormat, "Must provide a JSON object");
1298 } 1305 }
1299 } 1306 }
1300 1307
1308
1309 // Storage commitment SCU ---------------------------------------------------
1310
1311 static void StorageCommitmentScu(RestApiPostCall& call)
1312 {
1313 static const char* const ORTHANC_RESOURCES = "Resources";
1314 static const char* const DICOM_INSTANCES = "DicomInstances";
1315 static const char* const SOP_CLASS_UID = "SOPClassUID";
1316 static const char* const SOP_INSTANCE_UID = "SOPInstanceUID";
1317
1318 ServerContext& context = OrthancRestApi::GetContext(call);
1319
1320 Json::Value json;
1321 if (!call.ParseJsonRequest(json) ||
1322 json.type() != Json::objectValue)
1323 {
1324 throw OrthancException(ErrorCode_BadFileFormat,
1325 "Must provide a JSON object with a list of resources");
1326 }
1327 else if (!json.isMember(ORTHANC_RESOURCES) &&
1328 !json.isMember(DICOM_INSTANCES))
1329 {
1330 throw OrthancException(ErrorCode_BadFileFormat,
1331 "Empty storage commitment request, one of these fields is mandatory: \"" +
1332 std::string(ORTHANC_RESOURCES) + "\" or \"" + std::string(DICOM_INSTANCES) + "\"");
1333 }
1334 else
1335 {
1336 std::list<std::string> sopClassUids, sopInstanceUids;
1337
1338 if (json.isMember(ORTHANC_RESOURCES))
1339 {
1340 const Json::Value& resources = json[ORTHANC_RESOURCES];
1341
1342 if (resources.type() != Json::arrayValue)
1343 {
1344 throw OrthancException(ErrorCode_BadFileFormat,
1345 "The \"" + std::string(ORTHANC_RESOURCES) +
1346 "\" field must provide an array of Orthanc resources");
1347 }
1348 else
1349 {
1350 for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
1351 {
1352 if (resources[i].type() != Json::stringValue)
1353 {
1354 throw OrthancException(ErrorCode_BadFileFormat,
1355 "The \"" + std::string(ORTHANC_RESOURCES) +
1356 "\" field must provide an array of strings, found: " + resources[i].toStyledString());
1357 }
1358
1359 std::list<std::string> instances;
1360 context.GetIndex().GetChildInstances(instances, resources[i].asString());
1361
1362 for (std::list<std::string>::const_iterator
1363 it = instances.begin(); it != instances.end(); ++it)
1364 {
1365 std::string sopClassUid, sopInstanceUid;
1366 DicomMap tags;
1367 if (context.LookupOrReconstructMetadata(sopClassUid, *it, MetadataType_Instance_SopClassUid) &&
1368 context.GetIndex().GetAllMainDicomTags(tags, *it) &&
1369 tags.LookupStringValue(sopInstanceUid, DICOM_TAG_SOP_INSTANCE_UID, false))
1370 {
1371 sopClassUids.push_back(sopClassUid);
1372 sopInstanceUids.push_back(sopInstanceUid);
1373 }
1374 else
1375 {
1376 throw OrthancException(ErrorCode_InternalError,
1377 "Cannot retrieve SOP Class/Instance UID of Orthanc instance: " + *it);
1378 }
1379 }
1380 }
1381 }
1382 }
1383
1384 if (json.isMember(DICOM_INSTANCES))
1385 {
1386 const Json::Value& instances = json[DICOM_INSTANCES];
1387
1388 if (instances.type() != Json::arrayValue)
1389 {
1390 throw OrthancException(ErrorCode_BadFileFormat,
1391 "The \"" + std::string(DICOM_INSTANCES) +
1392 "\" field must provide an array of DICOM instances");
1393 }
1394 else
1395 {
1396 for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
1397 {
1398 if (instances[i].type() == Json::arrayValue)
1399 {
1400 if (instances[i].size() != 2 ||
1401 instances[i][0].type() != Json::stringValue ||
1402 instances[i][1].type() != Json::stringValue)
1403 {
1404 throw OrthancException(ErrorCode_BadFileFormat,
1405 "An instance entry must provide an array with 2 strings: "
1406 "SOP Class UID and SOP Instance UID");
1407 }
1408 else
1409 {
1410 sopClassUids.push_back(instances[i][0].asString());
1411 sopInstanceUids.push_back(instances[i][1].asString());
1412 }
1413 }
1414 else if (instances[i].type() == Json::objectValue)
1415 {
1416 if (!instances[i].isMember(SOP_CLASS_UID) ||
1417 !instances[i].isMember(SOP_INSTANCE_UID) ||
1418 instances[i][SOP_CLASS_UID].type() != Json::stringValue ||
1419 instances[i][SOP_INSTANCE_UID].type() != Json::stringValue)
1420 {
1421 throw OrthancException(ErrorCode_BadFileFormat,
1422 "An instance entry must provide an object with 2 string fiels: "
1423 "\"" + std::string(SOP_CLASS_UID) + "\" and \"" +
1424 std::string(SOP_INSTANCE_UID));
1425 }
1426 else
1427 {
1428 sopClassUids.push_back(instances[i][SOP_CLASS_UID].asString());
1429 sopInstanceUids.push_back(instances[i][SOP_INSTANCE_UID].asString());
1430 }
1431 }
1432 else
1433 {
1434 throw OrthancException(ErrorCode_BadFileFormat,
1435 "JSON array or object is expected to specify one "
1436 "instance to be queried, found: " + instances[i].toStyledString());
1437 }
1438 }
1439 }
1440 }
1441
1442 if (sopClassUids.size() != sopInstanceUids.size())
1443 {
1444 throw OrthancException(ErrorCode_InternalError);
1445 }
1446
1447 const std::string transactionUid = Toolbox::GenerateDicomPrivateUniqueIdentifier();
1448
1449 if (sopClassUids.empty())
1450 {
1451 LOG(WARNING) << "Issuing an outgoing storage commitment request that is empty: " << transactionUid;
1452 }
1453
1454 {
1455 const RemoteModalityParameters remote =
1456 MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
1457
1458 const std::string& remoteAet = remote.GetApplicationEntityTitle();
1459 const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
1460
1461 // Create a "pending" storage commitment report BEFORE the
1462 // actual SCU call in order to avoid race conditions
1463 context.GetStorageCommitmentReports().Store(
1464 transactionUid, new StorageCommitmentReports::Report(remoteAet));
1465
1466 DicomUserConnection scu(localAet, remote);
1467
1468 std::vector<std::string> a(sopClassUids.begin(), sopClassUids.end());
1469 std::vector<std::string> b(sopInstanceUids.begin(), sopInstanceUids.end());
1470 scu.RequestStorageCommitment(transactionUid, a, b);
1471 }
1472
1473 Json::Value result = Json::objectValue;
1474 result["ID"] = transactionUid;
1475 result["Path"] = "/storage-commitment/" + transactionUid;
1476 call.GetOutput().AnswerJson(result);
1477 }
1478 }
1479
1480
1481 static void GetStorageCommitmentReport(RestApiGetCall& call)
1482 {
1483 ServerContext& context = OrthancRestApi::GetContext(call);
1484
1485 const std::string& transactionUid = call.GetUriComponent("id", "");
1486
1487 {
1488 StorageCommitmentReports::Accessor accessor(
1489 context.GetStorageCommitmentReports(), transactionUid);
1490
1491 if (accessor.IsValid())
1492 {
1493 Json::Value json;
1494 accessor.GetReport().Format(json);
1495 call.GetOutput().AnswerJson(json);
1496 }
1497 else
1498 {
1499 throw OrthancException(ErrorCode_InexistentItem,
1500 "No storage commitment transaction with UID: " + transactionUid);
1501 }
1502 }
1503 }
1504
1505
1506 static void RemoveAfterStorageCommitment(RestApiPostCall& call)
1507 {
1508 ServerContext& context = OrthancRestApi::GetContext(call);
1509
1510 const std::string& transactionUid = call.GetUriComponent("id", "");
1511
1512 {
1513 StorageCommitmentReports::Accessor accessor(
1514 context.GetStorageCommitmentReports(), transactionUid);
1515
1516 if (!accessor.IsValid())
1517 {
1518 throw OrthancException(ErrorCode_InexistentItem,
1519 "No storage commitment transaction with UID: " + transactionUid);
1520 }
1521 else if (accessor.GetReport().GetStatus() != StorageCommitmentReports::Report::Status_Success)
1522 {
1523 throw OrthancException(ErrorCode_BadSequenceOfCalls,
1524 "Cannot remove DICOM instances after failure "
1525 "in storage commitment transaction: " + transactionUid);
1526 }
1527 else
1528 {
1529 std::vector<std::string> sopInstanceUids;
1530 accessor.GetReport().GetSuccessSopInstanceUids(sopInstanceUids);
1531
1532 for (size_t i = 0; i < sopInstanceUids.size(); i++)
1533 {
1534 std::vector<std::string> orthancId;
1535 context.GetIndex().LookupIdentifierExact(
1536 orthancId, ResourceType_Instance, DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUids[i]);
1537
1538 for (size_t j = 0; j < orthancId.size(); j++)
1539 {
1540 LOG(INFO) << "Storage commitment - Removing SOP instance UID / Orthanc ID: "
1541 << sopInstanceUids[i] << " / " << orthancId[j];
1542
1543 Json::Value tmp;
1544 context.GetIndex().DeleteResource(tmp, orthancId[j], ResourceType_Instance);
1545 }
1546 }
1547
1548 call.GetOutput().AnswerBuffer("{}", MimeType_Json);
1549 }
1550 }
1551 }
1552
1301 1553
1302 void OrthancRestApi::RegisterModalities() 1554 void OrthancRestApi::RegisterModalities()
1303 { 1555 {
1304 Register("/modalities", ListModalities); 1556 Register("/modalities", ListModalities);
1305 Register("/modalities/{id}", ListModalityOperations); 1557 Register("/modalities/{id}", ListModalityOperations);
1340 Register("/peers/{id}", DeletePeer); 1592 Register("/peers/{id}", DeletePeer);
1341 Register("/peers/{id}/store", PeerStore); 1593 Register("/peers/{id}/store", PeerStore);
1342 Register("/peers/{id}/system", PeerSystem); 1594 Register("/peers/{id}/system", PeerSystem);
1343 1595
1344 Register("/modalities/{id}/find-worklist", DicomFindWorklist); 1596 Register("/modalities/{id}/find-worklist", DicomFindWorklist);
1597
1598 // Storage commitment
1599 Register("/modalities/{id}/storage-commitment", StorageCommitmentScu);
1600 Register("/storage-commitment/{id}", GetStorageCommitmentReport);
1601 Register("/storage-commitment/{id}/remove", RemoveAfterStorageCommitment);
1345 } 1602 }
1346 } 1603 }