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