comparison OrthancServer/Sources/ServerContext.cpp @ 4978:2cfa50d8eb60 more-tags

Speed-up handling of DicomModalitiesInStudy in C-Find and tools/find queries
author Alain Mazy <am@osimis.io>
date Wed, 20 Apr 2022 14:36:47 +0200
parents dad71e6da406
children d0c34145320c
comparison
equal deleted inserted replaced
4977:dad71e6da406 4978:2cfa50d8eb60
62 * locking. 62 * locking.
63 **/ 63 **/
64 64
65 namespace Orthanc 65 namespace Orthanc
66 { 66 {
67 static void ComputeStudyTags(ExpandedResource& resource,
68 ServerContext& context,
69 const std::string& studyPublicId,
70 const std::set<DicomTag>& requestedTags);
71
72
67 static bool IsUncompressedTransferSyntax(DicomTransferSyntax transferSyntax) 73 static bool IsUncompressedTransferSyntax(DicomTransferSyntax transferSyntax)
68 { 74 {
69 return (transferSyntax == DicomTransferSyntax_LittleEndianImplicit || 75 return (transferSyntax == DicomTransferSyntax_LittleEndianImplicit ||
70 transferSyntax == DicomTransferSyntax_LittleEndianExplicit || 76 transferSyntax == DicomTransferSyntax_LittleEndianExplicit ||
71 transferSyntax == DicomTransferSyntax_BigEndianExplicit); 77 transferSyntax == DicomTransferSyntax_BigEndianExplicit);
1352 return false; 1358 return false;
1353 #endif 1359 #endif
1354 } 1360 }
1355 1361
1356 1362
1357 void ServerContext::ApplyInternal(ILookupVisitor& visitor, 1363 void ServerContext::Apply(ILookupVisitor& visitor,
1358 const DatabaseLookup& lookup, 1364 const DatabaseLookup& lookup,
1359 ResourceType queryLevel, 1365 ResourceType queryLevel,
1360 size_t since, 1366 size_t since,
1361 size_t limit) 1367 size_t limit)
1362 { 1368 {
1363 unsigned int databaseLimit = (queryLevel == ResourceType_Instance ? 1369 unsigned int databaseLimit = (queryLevel == ResourceType_Instance ?
1364 limitFindInstances_ : limitFindResults_); 1370 limitFindInstances_ : limitFindResults_);
1365 1371
1366 std::vector<std::string> resources, instances; 1372 std::vector<std::string> resources, instances;
1373 const DicomTagConstraint* dicomModalitiesConstraint = NULL;
1374
1375 bool hasModalitiesInStudyLookup = (queryLevel == ResourceType_Study &&
1376 lookup.GetConstraint(dicomModalitiesConstraint, DICOM_TAG_MODALITIES_IN_STUDY) &&
1377 ((dicomModalitiesConstraint->GetConstraintType() == ConstraintType_Equal && !dicomModalitiesConstraint->GetValue().empty()) ||
1378 (dicomModalitiesConstraint->GetConstraintType() == ConstraintType_List && !dicomModalitiesConstraint->GetValues().empty())));
1379
1380 std::unique_ptr<DatabaseLookup> fastLookup(lookup.Clone());
1381
1382 if (hasModalitiesInStudyLookup)
1383 {
1384 fastLookup->RemoveConstraint(DICOM_TAG_MODALITIES_IN_STUDY);
1385 }
1367 1386
1368 { 1387 {
1369 const size_t lookupLimit = (databaseLimit == 0 ? 0 : databaseLimit + 1); 1388 const size_t lookupLimit = (databaseLimit == 0 ? 0 : databaseLimit + 1);
1370 GetIndex().ApplyLookupResources(resources, &instances, lookup, queryLevel, lookupLimit); 1389 GetIndex().ApplyLookupResources(resources, &instances, *fastLookup, queryLevel, lookupLimit);
1371 } 1390 }
1372 1391
1373 bool complete = (databaseLimit == 0 || 1392 bool complete = (databaseLimit == 0 ||
1374 resources.size() <= databaseLimit); 1393 resources.size() <= databaseLimit);
1375 1394
1398 DicomMap dicom; 1417 DicomMap dicom;
1399 DicomMap allMainDicomTagsFromDB; 1418 DicomMap allMainDicomTagsFromDB;
1400 1419
1401 if (findStorageAccessMode_ == FindStorageAccessMode_DatabaseOnly || 1420 if (findStorageAccessMode_ == FindStorageAccessMode_DatabaseOnly ||
1402 findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer || 1421 findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer ||
1403 lookup.HasOnlyMainDicomTags()) 1422 fastLookup->HasOnlyMainDicomTags())
1404 { 1423 {
1405 // Case (1): The main DICOM tags, as stored in the database, 1424 // Case (1): The main DICOM tags, as stored in the database,
1406 // are sufficient to look for match 1425 // are sufficient to look for match
1407 1426
1408 if (!GetIndex().GetAllMainDicomTags(allMainDicomTagsFromDB, instances[i])) 1427 if (!GetIndex().GetAllMainDicomTags(allMainDicomTagsFromDB, instances[i]))
1447 1466
1448 // This map contains the entire JSON, i.e. more than the main DICOM tags 1467 // This map contains the entire JSON, i.e. more than the main DICOM tags
1449 hasOnlyMainDicomTags = false; 1468 hasOnlyMainDicomTags = false;
1450 } 1469 }
1451 1470
1452 if (lookup.IsMatch(dicom)) 1471 if (fastLookup->IsMatch(dicom))
1453 { 1472 {
1454 if (skipped < since) 1473 bool isMatch = true;
1455 { 1474
1456 skipped++; 1475 if (hasModalitiesInStudyLookup)
1457 } 1476 {
1458 else if (limit != 0 && 1477 std::set<DicomTag> requestedTags;
1459 countResults >= limit) 1478 requestedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY);
1460 { 1479 ExpandedResource resource;
1461 // Too many results, don't mark as complete 1480 ComputeStudyTags(resource, *this, resources[i], requestedTags);
1462 complete = false; 1481
1463 break; 1482 std::vector<std::string> modalities;
1464 } 1483 Toolbox::TokenizeString(modalities, resource.tags_.GetValue(DICOM_TAG_MODALITIES_IN_STUDY).GetContent(), '\\');
1465 else 1484 bool hasAtLeastOneModalityMatching = false;
1466 { 1485 for (size_t m = 0; m < modalities.size(); m++)
1467 if ((findStorageAccessMode_ == FindStorageAccessMode_DiskOnLookupAndAnswer ||
1468 findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer) &&
1469 dicomAsJson.get() == NULL &&
1470 isDicomAsJsonNeeded)
1471 { 1486 {
1472 dicomAsJson.reset(new Json::Value); 1487 hasAtLeastOneModalityMatching |= dicomModalitiesConstraint->IsMatch(modalities[m]);
1473 ReadDicomAsJson(*dicomAsJson, instances[i]);
1474 } 1488 }
1475 1489
1476 if (hasOnlyMainDicomTags) 1490 isMatch = isMatch && hasAtLeastOneModalityMatching;
1491 // copy the value of ModalitiesInStudy such that it can be reused to build the answer
1492 allMainDicomTagsFromDB.SetValue(DICOM_TAG_MODALITIES_IN_STUDY, resource.tags_.GetValue(DICOM_TAG_MODALITIES_IN_STUDY));
1493 }
1494
1495 if (isMatch)
1496 {
1497 if (skipped < since)
1477 { 1498 {
1478 // This is Case (1): The variable "dicom" only contains the main DICOM tags 1499 skipped++;
1479 visitor.Visit(resources[i], instances[i], allMainDicomTagsFromDB, dicomAsJson.get()); 1500 }
1501 else if (limit != 0 &&
1502 countResults >= limit)
1503 {
1504 // Too many results, don't mark as complete
1505 complete = false;
1506 break;
1480 } 1507 }
1481 else 1508 else
1482 { 1509 {
1483 // Remove the non-main DICOM tags from "dicom" if Case (2) 1510 if ((findStorageAccessMode_ == FindStorageAccessMode_DiskOnLookupAndAnswer ||
1484 // was used, for consistency with Case (1) 1511 findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer) &&
1485 1512 dicomAsJson.get() == NULL &&
1486 DicomMap mainDicomTags; 1513 isDicomAsJsonNeeded)
1487 mainDicomTags.ExtractMainDicomTags(dicom); 1514 {
1488 visitor.Visit(resources[i], instances[i], mainDicomTags, dicomAsJson.get()); 1515 dicomAsJson.reset(new Json::Value);
1516 ReadDicomAsJson(*dicomAsJson, instances[i]);
1517 }
1518
1519 if (hasOnlyMainDicomTags)
1520 {
1521 // This is Case (1): The variable "dicom" only contains the main DICOM tags
1522 visitor.Visit(resources[i], instances[i], allMainDicomTagsFromDB, dicomAsJson.get());
1523 }
1524 else
1525 {
1526 // Remove the non-main DICOM tags from "dicom" if Case (2)
1527 // was used, for consistency with Case (1)
1528
1529 DicomMap mainDicomTags;
1530 mainDicomTags.ExtractMainDicomTags(dicom);
1531 visitor.Visit(resources[i], instances[i], mainDicomTags, dicomAsJson.get());
1532 }
1533
1534 countResults ++;
1489 } 1535 }
1490
1491 countResults ++;
1492 } 1536 }
1493 } 1537 }
1494 } 1538 }
1495 1539
1496 if (complete) 1540 if (complete)
1498 visitor.MarkAsComplete(); 1542 visitor.MarkAsComplete();
1499 } 1543 }
1500 1544
1501 LOG(INFO) << "Number of matching resources: " << countResults; 1545 LOG(INFO) << "Number of matching resources: " << countResults;
1502 } 1546 }
1503
1504
1505
1506 namespace
1507 {
1508 class ModalitiesInStudyVisitor : public ServerContext::ILookupVisitor
1509 {
1510 private:
1511 class Study : public boost::noncopyable
1512 {
1513 private:
1514 std::string orthancId_;
1515 std::string instanceId_;
1516 DicomMap mainDicomTags_;
1517 Json::Value dicomAsJson_;
1518 std::set<std::string> modalitiesInStudy_;
1519
1520 public:
1521 Study(const std::string& instanceId,
1522 const DicomMap& seriesTags) :
1523 instanceId_(instanceId),
1524 dicomAsJson_(Json::nullValue)
1525 {
1526 {
1527 DicomMap tmp;
1528 tmp.Assign(seriesTags);
1529 tmp.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "dummy", false);
1530 DicomInstanceHasher hasher(tmp);
1531 orthancId_ = hasher.HashStudy();
1532 }
1533
1534 mainDicomTags_.MergeMainDicomTags(seriesTags, ResourceType_Study);
1535 mainDicomTags_.MergeMainDicomTags(seriesTags, ResourceType_Patient);
1536 AddModality(seriesTags);
1537 }
1538
1539 void AddModality(const DicomMap& seriesTags)
1540 {
1541 std::string modality;
1542 if (seriesTags.LookupStringValue(modality, DICOM_TAG_MODALITY, false) &&
1543 !modality.empty())
1544 {
1545 modalitiesInStudy_.insert(modality);
1546 }
1547 }
1548
1549 void SetDicomAsJson(const Json::Value& dicomAsJson)
1550 {
1551 dicomAsJson_ = dicomAsJson;
1552 }
1553
1554 const std::string& GetOrthancId() const
1555 {
1556 return orthancId_;
1557 }
1558
1559 const std::string& GetInstanceId() const
1560 {
1561 return instanceId_;
1562 }
1563
1564 const DicomMap& GetMainDicomTags() const
1565 {
1566 return mainDicomTags_;
1567 }
1568
1569 const Json::Value* GetDicomAsJson() const
1570 {
1571 if (dicomAsJson_.type() == Json::nullValue)
1572 {
1573 return NULL;
1574 }
1575 else
1576 {
1577 return &dicomAsJson_;
1578 }
1579 }
1580 };
1581
1582 typedef std::map<std::string, Study*> Studies;
1583
1584 bool isDicomAsJsonNeeded_;
1585 bool complete_;
1586 Studies studies_;
1587
1588 public:
1589 explicit ModalitiesInStudyVisitor(bool isDicomAsJsonNeeded) :
1590 isDicomAsJsonNeeded_(isDicomAsJsonNeeded),
1591 complete_(false)
1592 {
1593 }
1594
1595 ~ModalitiesInStudyVisitor()
1596 {
1597 for (Studies::const_iterator it = studies_.begin(); it != studies_.end(); ++it)
1598 {
1599 assert(it->second != NULL);
1600 delete it->second;
1601 }
1602
1603 studies_.clear();
1604 }
1605
1606 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
1607 {
1608 return isDicomAsJsonNeeded_;
1609 }
1610
1611 virtual void MarkAsComplete() ORTHANC_OVERRIDE
1612 {
1613 complete_ = true;
1614 }
1615
1616 virtual void Visit(const std::string& publicId,
1617 const std::string& instanceId,
1618 const DicomMap& seriesTags,
1619 const Json::Value* dicomAsJson) ORTHANC_OVERRIDE
1620 {
1621 std::string studyInstanceUid;
1622 if (seriesTags.LookupStringValue(studyInstanceUid, DICOM_TAG_STUDY_INSTANCE_UID, false))
1623 {
1624 Studies::iterator found = studies_.find(studyInstanceUid);
1625 if (found == studies_.end())
1626 {
1627 // New study
1628 std::unique_ptr<Study> study(new Study(instanceId, seriesTags));
1629
1630 if (dicomAsJson != NULL)
1631 {
1632 study->SetDicomAsJson(*dicomAsJson);
1633 }
1634
1635 studies_[studyInstanceUid] = study.release();
1636 }
1637 else
1638 {
1639 // Already existing study
1640 found->second->AddModality(seriesTags);
1641 }
1642 }
1643 }
1644
1645 void Forward(ILookupVisitor& callerVisitor,
1646 size_t since,
1647 size_t limit) const
1648 {
1649 size_t index = 0;
1650 size_t countForwarded = 0;
1651
1652 for (Studies::const_iterator it = studies_.begin(); it != studies_.end(); ++it, index++)
1653 {
1654 if (limit == 0 ||
1655 (index >= since &&
1656 index < limit))
1657 {
1658 assert(it->second != NULL);
1659 const Study& study = *it->second;
1660
1661 countForwarded++;
1662 callerVisitor.Visit(study.GetOrthancId(), study.GetInstanceId(),
1663 study.GetMainDicomTags(), study.GetDicomAsJson());
1664 }
1665 }
1666
1667 if (countForwarded == studies_.size())
1668 {
1669 callerVisitor.MarkAsComplete();
1670 }
1671 }
1672 };
1673
1674 # if 1
1675 class StudyInstanceUidVisitor : public ServerContext::ILookupVisitor
1676 {
1677 private:
1678 std::set<std::string> studyInstanceUids;
1679
1680 public:
1681 explicit StudyInstanceUidVisitor()
1682 {
1683 }
1684
1685 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
1686 {
1687 return false;
1688 }
1689
1690 virtual void MarkAsComplete() ORTHANC_OVERRIDE
1691 {
1692 }
1693
1694 virtual void Visit(const std::string& publicId,
1695 const std::string& instanceId,
1696 const DicomMap& mainDicomTags,
1697 const Json::Value* dicomAsJson) ORTHANC_OVERRIDE
1698 {
1699 std::string studyInstanceUid;
1700 if (!mainDicomTags.LookupStringValue(studyInstanceUid, DICOM_TAG_STUDY_INSTANCE_UID, false))
1701 {
1702 throw OrthancException(ErrorCode_InternalError);
1703 }
1704 studyInstanceUids.insert(studyInstanceUid);
1705 }
1706
1707 const std::set<std::string>& GetFilteredStudyInstanceUids() const
1708 {
1709 return studyInstanceUids;
1710 }
1711 };
1712 }
1713
1714 void ServerContext::Apply(ILookupVisitor& visitor,
1715 const DatabaseLookup& lookup,
1716 ResourceType queryLevel,
1717 size_t since,
1718 size_t limit)
1719 {
1720 const DicomTagConstraint* constraint = NULL;
1721
1722 if (queryLevel == ResourceType_Study &&
1723 lookup.GetConstraint(constraint, DICOM_TAG_MODALITIES_IN_STUDY) &&
1724 ((constraint->GetConstraintType() == ConstraintType_Equal && !constraint->GetValue().empty()) ||
1725 (constraint->GetConstraintType() == ConstraintType_List && !constraint->GetValues().empty()))
1726 )
1727 {
1728 std::unique_ptr<DatabaseLookup> studiesPreFilterLookup(lookup.Clone());
1729 studiesPreFilterLookup->RemoveConstraint(DICOM_TAG_MODALITIES_IN_STUDY);
1730
1731 DatabaseLookup seriesLookup;
1732
1733 std::set<std::string> filteredStudyInstanceUids;
1734 if (studiesPreFilterLookup->GetConstraintsCount() >= 1)
1735 {
1736 LOG(INFO) << "Performing First filtering without ModalitiesInStudy";
1737
1738 StudyInstanceUidVisitor studyVisitor;
1739
1740 ApplyInternal(studyVisitor, *studiesPreFilterLookup, queryLevel, since, limit);
1741
1742 DicomTagConstraint studyInstanceUidsConstraint(DICOM_TAG_STUDY_INSTANCE_UID, ConstraintType_List, true, true);
1743 for (std::set<std::string>::const_iterator it = studyVisitor.GetFilteredStudyInstanceUids().begin();
1744 it != studyVisitor.GetFilteredStudyInstanceUids().end(); it++)
1745 {
1746 studyInstanceUidsConstraint.AddValue(*it);
1747 }
1748
1749 seriesLookup.AddConstraint(studyInstanceUidsConstraint);
1750 }
1751
1752 // Convert the study-level query, into a series-level query,
1753 // where "ModalitiesInStudy" is replaced by "Modality"
1754 // and where all other constraints are replaced by "StudyInstanceUID IN (...)"
1755
1756 DicomTagConstraint modality(*constraint);
1757 modality.SetTag(DICOM_TAG_MODALITY);
1758 seriesLookup.AddConstraint(modality);
1759
1760 ModalitiesInStudyVisitor seriesVisitor(visitor.IsDicomAsJsonNeeded());
1761 ApplyInternal(seriesVisitor, seriesLookup, ResourceType_Series, 0, 0);
1762 seriesVisitor.Forward(visitor, since, limit);
1763 }
1764 else // filtering without ModalitiesInStudy
1765 {
1766 ApplyInternal(visitor, lookup, queryLevel, since, limit);
1767 }
1768 }
1769
1770 #else
1771 void ServerContext::Apply(ILookupVisitor& visitor,
1772 const DatabaseLookup& lookup,
1773 ResourceType queryLevel,
1774 size_t since,
1775 size_t limit)
1776 {
1777 if (queryLevel == ResourceType_Study &&
1778 lookup.HasTag(DICOM_TAG_MODALITIES_IN_STUDY))
1779 {
1780 // Convert the study-level query, into a series-level query,
1781 // where "ModalitiesInStudy" is replaced by "Modality"
1782 DatabaseLookup seriesLookup;
1783
1784 for (size_t i = 0; i < lookup.GetConstraintsCount(); i++)
1785 {
1786 const DicomTagConstraint& constraint = lookup.GetConstraint(i);
1787 if (constraint.GetTag() == DICOM_TAG_MODALITIES_IN_STUDY)
1788 {
1789 if ((constraint.GetConstraintType() == ConstraintType_Equal && constraint.GetValue().empty()) ||
1790 (constraint.GetConstraintType() == ConstraintType_List && constraint.GetValues().empty()))
1791 {
1792 // Ignore universal lookup on "ModalitiesInStudy" (0008,0061),
1793 // this should have been handled by the caller
1794 ApplyInternal(visitor, lookup, queryLevel, since, limit);
1795 return;
1796 }
1797 else
1798 {
1799 DicomTagConstraint modality(constraint);
1800 modality.SetTag(DICOM_TAG_MODALITY);
1801 seriesLookup.AddConstraint(modality);
1802 }
1803 }
1804 else
1805 {
1806 seriesLookup.AddConstraint(constraint);
1807 }
1808 }
1809
1810 ModalitiesInStudyVisitor seriesVisitor(visitor.IsDicomAsJsonNeeded());
1811 ApplyInternal(seriesVisitor, seriesLookup, ResourceType_Series, 0, 0);
1812 seriesVisitor.Forward(visitor, since, limit);
1813 }
1814 else
1815 {
1816 ApplyInternal(visitor, lookup, queryLevel, since, limit);
1817 }
1818 }
1819 #endif
1820 1547
1821 bool ServerContext::LookupOrReconstructMetadata(std::string& target, 1548 bool ServerContext::LookupOrReconstructMetadata(std::string& target,
1822 const std::string& publicId, 1549 const std::string& publicId,
1823 ResourceType level, 1550 ResourceType level,
1824 MetadataType metadata) 1551 MetadataType metadata)
2643 return true; 2370 return true;
2644 } 2371 }
2645 } 2372 }
2646 2373
2647 if (expandFlags != ExpandResourceDbFlags_None 2374 if (expandFlags != ExpandResourceDbFlags_None
2648 && GetIndex().ExpandResource(resource, publicId, level, requestedTags, expandFlags)) 2375 && GetIndex().ExpandResource(resource, publicId, level, requestedTags, static_cast<ExpandResourceDbFlags>(expandFlags | ExpandResourceDbFlags_IncludeMetadata))) // we always need the metadata to get the mainDicomTagsSignature
2649 { 2376 {
2650 // check the main dicom tags list has not changed since the resource was stored 2377 // check the main dicom tags list has not changed since the resource was stored
2651 if (resource.mainDicomTagsSignature_ != DicomMap::GetMainDicomTagsSignature(resource.type_)) 2378 if (resource.mainDicomTagsSignature_ != DicomMap::GetMainDicomTagsSignature(resource.type_))
2652 { 2379 {
2653 OrthancConfiguration::ReaderLock lock; 2380 OrthancConfiguration::ReaderLock lock;