diff --git a/Modules/Core/include/mitkSourceImageRelationRule.h b/Modules/Core/include/mitkSourceImageRelationRule.h index e6179cca4b..b631e5b3d8 100644 --- a/Modules/Core/include/mitkSourceImageRelationRule.h +++ b/Modules/Core/include/mitkSourceImageRelationRule.h @@ -1,173 +1,164 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkGenericIDRelationRule_h #define mitkGenericIDRelationRule_h #include "mitkPropertyRelationRuleBase.h" #include "mitkImage.h" -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4251) -#endif - namespace mitk { /**This rule class can be used for relations that reference an image as source for a destination entity. (e.g. an image that is used to generate the relation source). The ID-layer is supported like for GenericIDReleations. So it can be used for all ID based relations between PropertyProviders that also implement the interface identifiable. In addition the rule uses the data-layer to deduce/define relations. For this layer it uses properties compliant to DICOM. Thus (1) the information is stored in a DICOM Source Image Sequence item (0x0008,0x2112) and (2) the destination must have properties DICOM SOP Instance UIDs (0x0008, 0x0018) and DICOM SOP Class UID (0x0008, 0x0016). If the destination does not have this properties, no connection can be made on the data-layer. @remark Please note that PropertyRelationRules and DICOM use the term "source" differently. The DICOM source (image) equals the PropertyRelationRule destination. This is due to an inverted relation direction. So in the context of the SourceImageRelationRule interface a derived data is the source and points to the original image, it derives from. In the context of DICOM this referenced original image would be called source image (as the name of this class). In order to be able to use this class for different relation types (DICOM would call it purposes), the purposeTag is used. It must be specified when creating a rule instance. The purposeTag will be used as suffix for the rule ID of the instance and therefore allows to create specific and distinguishable rules instances based on this class. One may also specify the display name and the role names of the instance. If not specified the default values are used (display name: " relation", source role name: "derived data", destination role name: "source image") */ class MITKCORE_EXPORT SourceImageRelationRule : public mitk::PropertyRelationRuleBase { public: mitkClassMacro(SourceImageRelationRule, PropertyRelationRuleBase); itkNewMacro(Self); mitkNewMacro1Param(Self, const RuleIDType &); mitkNewMacro2Param(Self, const RuleIDType &, const std::string &); mitkNewMacro4Param(Self, const RuleIDType &, const std::string &, const std::string &, const std::string &); using RuleIDType = PropertyRelationRuleBase::RuleIDType; using RelationUIDType = PropertyRelationRuleBase::RelationUIDType; using RelationUIDVectorType = PropertyRelationRuleBase::RelationUIDVectorType; /** Returns an ID string that identifies the rule class */ RuleIDType GetRuleID() const override; bool IsAbstract() const override; /** Returns a human readable string that can be used to describe the rule. Does not need to be unique.*/ std::string GetDisplayName() const override; /** Returns a human readable string that can be used to describe the role of a source in context of the rule * instance.*/ std::string GetSourceRoleName() const override; /** Returns a human readable string that can be used to describe the role of a destination in context of the rule * instance.*/ std::string GetDestinationRoleName() const override; bool IsDestinationCandidate(const IPropertyProvider *owner) const override; /** Connects to passed images. @remark destination must specifiy DICOM SOP Instance UIDs (0x0008, 0x0018) and DICOM SOP Class UID (0x0008, 0x0016) in order to establish a connection on the data layer.*/ RelationUIDType Connect(Image *source, const Image *destination) const; protected: SourceImageRelationRule(); SourceImageRelationRule(const RuleIDType &purposeTag); SourceImageRelationRule(const RuleIDType &purposeTag, const std::string &displayName); SourceImageRelationRule(const RuleIDType &purposeTag, const std::string &displayName, const std::string &sourceRole, const std::string &destinationRole); ~SourceImageRelationRule() override = default; using InstanceIDType = PropertyRelationRuleBase::InstanceIDType; using InstanceIDVectorType = PropertyRelationRuleBase::InstanceIDVectorType; /** Helper function that returns a vector of all selections of the property DICOM.0008.2112 that refer to destination or all (if no destination is passed).*/ std::vector GetReferenceSequenceIndices(const IPropertyProvider * source, const IPropertyProvider * destination = nullptr) const; /** Is called if a instance ID cannot be deduced on the ID-layer. Implement this method to check which existing relation(s) as Connected_Data exists between both passed instances. If the passed instances have no explicit relation of type Connected_Data, an empty vector will be returned. @remark Per definition of property relation rules only 0 or 1 instance should be found for one provider pair and rule. But the data layer may be ambiguous and there for muliple relation instances of the rule instance could match. The implementation of this function should report all relation instances. The calling function will take care of this violation. @pre source must be a pointer to a valid IPropertyProvider instance. @pre destination must be a pointer to a valid IPropertyProvider instance.*/ InstanceIDVectorType GetInstanceID_datalayer(const IPropertyProvider *source, const IPropertyProvider *destination) const override; /** Is called by HasRelation() if no relation of type Connected_ID (GetInstanceID_IDLayer()) or Connected_Data (GetInstanceID_datalayer()) is evident. Implement this method to deduce if the passed instances have a relation of type Implicit_Data. @pre source must be a pointer to a valid IPropertyProvider instance. @pre destination must be a pointer to a valid IPropertyProvider instance. */ bool HasImplicitDataRelation(const IPropertyProvider *source, const IPropertyProvider *destination) const override; /**Is called by Connect() to ensure that source has correctly set properties to resemble the relation on the data layer. This means that the method should set the properties that describe and encode the relation on the data layer (data-layer-specific relation properties). If the passed instance are already connected, the old settings should be overwritten. Connect() will ensure that source and destination are valid pointers. @param instanceID is the ID for the relation instance that should be connected. Existance of the relation instance is ensured. @pre source must be a valid instance. @pre destination must be a valid instance.*/ void Connect_datalayer(IPropertyOwner *source, const IPropertyProvider *destination, const InstanceIDType &instanceID) const override; /**This method is called by Disconnect() to remove all properties of the relation from the source that are set by Connect_datalayer(). @remark All RII-properties of this relation will removed by Disconnect() after this method call. If the relationUID is not part of the source. Nothing will be changed. Disconnect() ensures that source is a valid pointer if called. @remark Disconnect() ensures that sourece is valid and only invokes if instance exists.*/ void Disconnect_datalayer(IPropertyOwner *source, const InstanceIDType &instanceID) const override; virtual bool IsSupportedRuleID(const RuleIDType& ruleID) const override; itk::LightObject::Pointer InternalClone() const override; /**Prepares a new reference to an image on the data layer. Therefore an unused and valid sequence item index for the passed source will be genarated and a relationUID property with the relationUID will be set to block the instance ID. The instance ID will be returned. @remark The method is guarded by a class wide mutex to avoid racing conditions in a scenario where rules are used concurrently.*/ PropertyKeyPath::ItemSelectionIndex CreateNewSourceImageSequenceItem(IPropertyOwner *source) const; private: RuleIDType m_PurposeTag; std::string m_DisplayName; std::string m_SourceRole; std::string m_DestinationRole; }; } // namespace mitk -#ifdef _MSC_VER -#pragma warning(pop) -#endif - #endif diff --git a/Modules/Core/src/DataManagement/mitkSourceImageRelationRule.cpp b/Modules/Core/src/DataManagement/mitkSourceImageRelationRule.cpp index 890dc1e773..6bc57462bc 100644 --- a/Modules/Core/src/DataManagement/mitkSourceImageRelationRule.cpp +++ b/Modules/Core/src/DataManagement/mitkSourceImageRelationRule.cpp @@ -1,379 +1,380 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include "mitkSourceImageRelationRule.h" #include "mitkPropertyNameHelper.h" #include "mitkStringProperty.h" #include "mitkTemporoSpatialStringProperty.h" #include "mitkDataNode.h" bool mitk::SourceImageRelationRule::IsAbstract() const { return m_PurposeTag.empty(); }; bool mitk::SourceImageRelationRule::IsSupportedRuleID(const RuleIDType& ruleID) const { return ruleID == this->GetRuleID() || (IsAbstract() && ruleID.find("SourceImageRelation ") == 0); }; mitk::SourceImageRelationRule::RuleIDType mitk::SourceImageRelationRule::GetRuleID() const { return "SourceImageRelation " + m_PurposeTag; }; std::string mitk::SourceImageRelationRule::GetDisplayName() const { return m_DisplayName; }; std::string mitk::SourceImageRelationRule::GetSourceRoleName() const { return m_SourceRole; }; std::string mitk::SourceImageRelationRule::GetDestinationRoleName() const { return m_DestinationRole; }; bool mitk::SourceImageRelationRule::IsDestinationCandidate(const IPropertyProvider *owner) const { auto node = dynamic_cast(owner); - auto image = dynamic_cast(owner); - if (node) - { - image = dynamic_cast(node->GetData()); - } + + auto image = nullptr != node + ? dynamic_cast(node->GetData()) + : dynamic_cast(owner); return image != nullptr; } mitk::SourceImageRelationRule::RelationUIDType mitk::SourceImageRelationRule::Connect(Image *source, const Image *destination) const { return Superclass::Connect(source, destination); }; mitk::SourceImageRelationRule::SourceImageRelationRule() : m_PurposeTag(""), m_DisplayName("Abstract image to image relation"), m_SourceRole("derived data"), m_DestinationRole("source image") {}; mitk::SourceImageRelationRule::SourceImageRelationRule(const RuleIDType &purposeTag) : SourceImageRelationRule(purposeTag, purposeTag + " relation"){}; mitk::SourceImageRelationRule::SourceImageRelationRule(const RuleIDType &purposeTag, const std::string &displayName) : SourceImageRelationRule( purposeTag, displayName, "derived data", "source image"){}; mitk::SourceImageRelationRule::SourceImageRelationRule(const RuleIDType &purposeTag, const std::string &displayName, const std::string &sourceRole, const std::string &destinationRole) : m_PurposeTag(purposeTag), m_DisplayName(displayName), m_SourceRole(sourceRole), m_DestinationRole(destinationRole){}; mitk::SourceImageRelationRule::InstanceIDVectorType mitk::SourceImageRelationRule::GetInstanceID_datalayer( const IPropertyProvider * source, const IPropertyProvider * destination) const { InstanceIDVectorType result; auto relevantReferenceIndices = GetReferenceSequenceIndices(source, destination); auto itemRegExStr = this->GetRIIPropertyRegEx("SourceImageSequenceItem"); auto regEx = std::regex(itemRegExStr); - std::vector keys; //workaround until T24729 is done. Please remove if T24728 is done - keys = PropertyRelationRuleBase::GetPropertyKeys(source); + auto keys = PropertyRelationRuleBase::GetPropertyKeys(source); //end workaround for T24729 for (const auto &key : keys) { if (std::regex_match(key, regEx)) { auto sequItemProp = source->GetConstProperty(key); if (sequItemProp.IsNotNull()) { auto finding = std::find(std::cbegin(relevantReferenceIndices), std::cend(relevantReferenceIndices), sequItemProp->GetValueAsString()); if (finding != std::cend(relevantReferenceIndices)) { auto instanceID = GetInstanceIDByPropertyName(key); auto ruleID = GetRuleIDByInstanceID(source, instanceID); if (this->IsSupportedRuleID(ruleID)) { result.push_back(instanceID); } } } } } return result; }; std::vector mitk::SourceImageRelationRule::GetReferenceSequenceIndices(const IPropertyProvider * source, const IPropertyProvider * destination) const { std::vector result; BaseProperty::ConstPointer destInstanceUIDProp; if (destination) { destInstanceUIDProp = destination->GetConstProperty(GeneratePropertyNameForDICOMTag(0x0008, 0x0018)); if (destInstanceUIDProp.IsNull()) { return result; } } PropertyKeyPath referencedInstanceUIDs; referencedInstanceUIDs.AddElement("DICOM").AddElement("0008").AddAnySelection("2112").AddElement("0008").AddElement("1155"); auto sourceRegExStr = PropertyKeyPathToPropertyRegEx(referencedInstanceUIDs);; auto regEx = std::regex(sourceRegExStr); std::vector keys; //workaround until T24729 is done. Please remove if T24728 is done keys = PropertyRelationRuleBase::GetPropertyKeys(source); //end workaround for T24729 for (const auto &key : keys) { if (std::regex_match(key, regEx)) { auto refUIDProp = source->GetConstProperty(key); if (destination==nullptr || *refUIDProp == *destInstanceUIDProp) { auto currentKeyPath = PropertyNameToPropertyKeyPath(key); auto currentKeyPathSelection = currentKeyPath.GetNode(2).selection; PropertyKeyPath purposePath; purposePath.AddElement("DICOM").AddElement("0008").AddSelection("2112", currentKeyPathSelection).AddElement("0040").AddSelection("a170", 0).AddElement("0008").AddElement("0104"); auto purposeProp = source->GetConstProperty(PropertyKeyPathToPropertyName(purposePath)); if (this->IsAbstract() || (purposeProp.IsNotNull() && purposeProp->GetValueAsString() == this->m_PurposeTag)) { result.push_back(std::to_string(currentKeyPathSelection)); } } } } return result; }; bool mitk::SourceImageRelationRule::HasImplicitDataRelation(const IPropertyProvider * source, const IPropertyProvider * destination) const { auto relevantReferences = GetReferenceSequenceIndices(source, destination); if (this->IsAbstract()) { return !relevantReferences.empty(); } else { for (auto referenceIndex : relevantReferences) { PropertyKeyPath purposePath; purposePath.AddElement("DICOM").AddElement("0008").AddSelection("2112", std::stoi(referenceIndex)).AddElement("0040").AddSelection("a170", 0).AddElement("0008").AddElement("0104"); auto purposeProp = source->GetConstProperty(PropertyKeyPathToPropertyName(purposePath)); if (purposeProp.IsNotNull() && purposeProp->GetValueAsString() == this->m_PurposeTag) { return true; } } } return false; }; -/**This mutex is used to guard mitk::PropertyRelationRuleBase::CreateNewRelationInstance by a class wide mutex to avoid +/**This mutex is used to guard mitk::SourceImageRelationRule::CreateNewSourceImageSequenceItem by a class wide mutex to avoid racing conditions in a scenario where rules are used concurrently. It is not in the class interface itself, because it is an implementation detail. */ -std::mutex sequenceItemCreationLock; +namespace +{ + std::mutex sequenceItemCreationLock; +} mitk::PropertyKeyPath::ItemSelectionIndex mitk::SourceImageRelationRule::CreateNewSourceImageSequenceItem( IPropertyOwner *source) const { std::lock_guard guard(sequenceItemCreationLock); ////////////////////////////////////// // Get all existing sequence items std::vector instanceIDs; PropertyKeyPath::ItemSelectionIndex newID = 0; PropertyKeyPath referencedInstanceUIDs; referencedInstanceUIDs.AddElement("DICOM").AddElement("0008").AddAnySelection("2112").AddElement("0008").AddElement("1155"); auto regExStr = PropertyKeyPathToPropertyRegEx(referencedInstanceUIDs); auto regEx = std::regex(regExStr); std::smatch instance_matches; //workaround until T24729 is done. You can use directly source->GetPropertyKeys again, when fixed. const auto keys = GetPropertyKeys(source); //end workaround for T24729 for (const auto &key : keys) { if (std::regex_search(key, instance_matches, regEx)) { if (instance_matches.size()>1) { instanceIDs.push_back(std::stoi(instance_matches[1])); } } } ////////////////////////////////////// // Get new ID std::sort(instanceIDs.begin(), instanceIDs.end()); if (!instanceIDs.empty()) { newID = instanceIDs.back()+1; } ////////////////////////////////////// // reserve new ID PropertyKeyPath newSourceImageSequencePath; newSourceImageSequencePath.AddElement("DICOM").AddElement("0008").AddSelection("2112",newID).AddElement("0008").AddElement("1155"); auto newKey = PropertyKeyPathToPropertyName(newSourceImageSequencePath); source->SetProperty(newKey, mitk::TemporoSpatialStringProperty::New("reserved slot for source image sequence")); return newID; }; void mitk::SourceImageRelationRule::Connect_datalayer(IPropertyOwner * source, const IPropertyProvider * destination, const InstanceIDType & instanceID) const { auto destInstanceUIDProp = destination->GetConstProperty(GeneratePropertyNameForDICOMTag(0x0008,0x0018)); auto destClassUIDProp = destination->GetConstProperty(GeneratePropertyNameForDICOMTag(0x0008, 0x0016)); if (destInstanceUIDProp.IsNotNull() && destClassUIDProp.IsNotNull()) { auto existingRefs = this->GetReferenceSequenceIndices(source, destination); std::string newSelectionIndexStr; if (!existingRefs.empty()) { newSelectionIndexStr = existingRefs[0]; } else { PropertyKeyPath::ItemSelectionIndex newSelectionIndex = CreateNewSourceImageSequenceItem(source); PropertyKeyPath refInstanceUIDPath; refInstanceUIDPath.AddElement("DICOM").AddElement("0008").AddSelection("2112", newSelectionIndex).AddElement("0008").AddElement("1155"); source->SetProperty(PropertyKeyPathToPropertyName(refInstanceUIDPath), destInstanceUIDProp->Clone()); PropertyKeyPath refClassUIDPath; refClassUIDPath.AddElement("DICOM").AddElement("0008").AddSelection("2112", newSelectionIndex).AddElement("0008").AddElement("1150"); source->SetProperty(PropertyKeyPathToPropertyName(refClassUIDPath), destClassUIDProp->Clone()); PropertyKeyPath purposePath; purposePath.AddElement("DICOM").AddElement("0008").AddSelection("2112", newSelectionIndex).AddElement("0040").AddSelection("a170", 0).AddElement("0008").AddElement("0104"); source->SetProperty(PropertyKeyPathToPropertyName(purposePath), StringProperty::New(m_PurposeTag)); newSelectionIndexStr = std::to_string(newSelectionIndex); } auto sourceImageRefPath = GetRootKeyPath().AddElement(instanceID).AddElement("SourceImageSequenceItem"); source->SetProperty(PropertyKeyPathToPropertyName(sourceImageRefPath), StringProperty::New(newSelectionIndexStr).GetPointer()); } else { MITK_DEBUG << "Cannot connect SourceImageRelationRule on data layer. Passed destination does not have properties for DICOM SOP Instance UIDs(0x0008, 0x0018) and DICOM SOP Class UID(0x0008, 0x0016)"; } }; void mitk::SourceImageRelationRule::Disconnect_datalayer(IPropertyOwner * source, const InstanceIDType & instanceID) const { auto sourceImageRefPath = GetRootKeyPath().AddElement(instanceID).AddElement("SourceImageSequenceItem"); auto imageRefProp = source->GetConstProperty(PropertyKeyPathToPropertyName(sourceImageRefPath)); if (imageRefProp.IsNotNull()) { auto deletedImageRefSequenceIndex = imageRefProp->GetValueAsString(); auto refs = GetReferenceSequenceIndices(source); std::sort(refs.begin(), refs.end()); for (auto refIndexStr : refs) { auto refIndex = std::stoi(refIndexStr); if (refIndex >= std::stoi(deletedImageRefSequenceIndex)) { PropertyKeyPath refDICOMDataPath; refDICOMDataPath.AddElement("DICOM").AddElement("0008").AddSelection("2112", refIndex); auto prefix = PropertyKeyPathToPropertyName(refDICOMDataPath); PropertyKeyPath refRelDataPath = GetRootKeyPath().AddAnyElement().AddElement("SourceImageSequenceItem");; auto regEx = std::regex(PropertyKeyPathToPropertyRegEx(refRelDataPath)); //workaround until T24729 is done. You can use directly source->GetPropertyKeys again, when fixed. const auto keys = GetPropertyKeys(source); //end workaround for T24729 for (const auto &key : keys) { if (key.find(prefix) == 0) { //its a relevant DICOM property delete or update if (refIndexStr != deletedImageRefSequenceIndex) { //reindex to close the gap in the dicom sequence. auto newPath = PropertyNameToPropertyKeyPath(key); newPath.GetNode(2).selection = refIndex - 1; source->SetProperty(PropertyKeyPathToPropertyName(newPath), source->GetNonConstProperty(key)); } //remove old/outdated data layer information source->RemoveProperty(key); auto sourceImageRefPath = GetRootKeyPath().AddElement(instanceID).AddElement("SourceImageSequenceItem"); } if (std::regex_match(key, regEx)) { auto imageSequenceItemProp = source->GetConstProperty(key); if (imageSequenceItemProp->GetValueAsString() == std::to_string(refIndex)) { //its a relevant data property of the relation rule. source->SetProperty(key, StringProperty::New(std::to_string(refIndex - 1))); } } } } } } }; itk::LightObject::Pointer mitk::SourceImageRelationRule::InternalClone() const { itk::LightObject::Pointer result = Self::New(this->m_PurposeTag, this->m_DisplayName, this->m_SourceRole, this->m_DestinationRole).GetPointer(); return result; };