I’m currently having difficulty filtering the subject hierarchy using the attribute filter API. The following illustrates the unexpected behavior upon using setAttributeFilter
. Does anyone know why this may be happening?
tree_view = slicer.qMRMLSubjectHierarchyTreeView()
tree_view.setMRMLScene(slicer.mrmlScene)
line_node_1 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsLineNode", "Line1")
line_node_2 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsLineNode", "Line2")
line_node_1.SetAttribute("Name", "Value")
tree_view.displayedItemCount()
is 2
tree_view.setAttributeFilter("Name", "Value")
tree_view.displayedItemCount()
is 0, although it was expected to be 1.
jcfr
(Jean Christophe Fillion Robin (Kitware))
October 27, 2023, 1:53am
2
cpinter
(Csaba Pinter)
October 27, 2023, 8:47am
3
The setAttributeFilter
method sets an attribute filter for items and not nodes. This is why when you set a node attribute but then were filtering on item attribute it did not show anything. It is also deprecated. See
/// Filter to show only items that contain an attribute with this name. Empty by default
/// Note: Deprecated, kept only for backwards compatibility. Sets and returns the first attribute in \sa includeNodeAttributeNamesFilter
Q_PROPERTY(QString attributeNameFilter READ attributeNameFilter WRITE setAttributeNameFilter)
Please use these properties instead:
/// Filter to show only items that contain any of the given attributes with this name. Empty by default.
/// When setting it, all the include filters are overwritten.
Q_PROPERTY(QStringList includeItemAttributeNamesFilter READ includeItemAttributeNamesFilter WRITE setIncludeItemAttributeNamesFilter)
/// Filter to show only items for data nodes that contain any of the given attributes with this name. Empty by default.
/// When setting it, all the include filters are overwritten.
Q_PROPERTY(QStringList includeNodeAttributeNamesFilter READ includeNodeAttributeNamesFilter WRITE setIncludeNodeAttributeNamesFilter)
/// Filter to hide items that contain any of the given attributes with this name. Empty by default.
/// When setting it, all the include filters are overwritten.
/// Overrides \sa includeItemAttributeNamesFilter
Q_PROPERTY(QStringList excludeItemAttributeNamesFilter READ excludeItemAttributeNamesFilter WRITE setExcludeItemAttributeNamesFilter)
/// Filter to hide items for data nodes that contain any of the given attributes with this name. Empty by default.
/// When setting it, all the include filters are overwritten.
/// Overrides \sa includeNodeAttributeNamesFilter
Q_PROPERTY(QStringList excludeNodeAttributeNamesFilter READ excludeNodeAttributeNamesFilter WRITE setExcludeNodeAttributeNamesFilter)
The node and item attribute filters have their different properties, as well as their include and exclude variants.
@cpinter Is that deprecation comment wrong in that it says Sets and returns the first attribute in \sa includeNodeAttributeNamesFilter
when actually it should be Sets and returns the first attribute in \sa includeItemAttributeNamesFilter
?
Also does this mean the non-deprecated API can only filter by attribute name and not also by attribute value like SetAttributeFilter
could do?
cpinter
(Csaba Pinter)
October 27, 2023, 12:06pm
5
You’re right, the comment is referencing the wrong function, it should be includeItemAttributeNamesFilter
.
No, you can still filter by value, see
/// Add single item attribute filter specifying attribute name, value, include/exclude, and class name
/// \param attributeName Name of the item attribute to filter
/// \param attributeValue Value of the item attribute to filter
/// \param include Flag indicating whether this is an include filter or exclude filter.
/// - Include filter means that only the items are shown that match the filter.
/// - Exclude filter hides items that match the filter. Overrides include filters.
/// True by default (i.e. include filter).
Q_INVOKABLE void addItemAttributeFilter(QString attributeName, QVariant attributeValue=QString(), bool include=true);
/// Remove single item attribute filter specifying each attribute \sa addAttributeFilter
Q_INVOKABLE void removeItemAttributeFilter(QString attributeName, QVariant attributeValue, bool include);
/// Remove all item attribute filters specifying a given attribute name and include flag
Q_INVOKABLE void removeItemAttributeFilter(QString attributeName, bool include);
/// Add single node attribute filter specifying attribute name, value, include/exclude, and class name
/// \param attributeName Name of the node attribute to filter
/// \param attributeValue Value of the node attribute to filter
/// \param include Flag indicating whether this is an include filter or exclude filter.
/// - Include filter means that only the items are shown that match the filter.
/// - Exclude filter hides items that match the filter. Overrides include filters.
/// True by default (i.e. include filter).
/// \param className Only filter attributes on a certain type. Empty by default (i.e. allow all classes)
This file has been truncated. show original
I think the best thing to do would be to fix that comment in (1), and add an actual deprecation warning in the bodies of setAttributeNameFilter
and setAttributeValueFilter
.
Yes I agree with those improvements.
Since there are some methods specific to items and others for nodes it was getting a bit confusing. I was thinking of “item” as a generic term about a thing in a list of things which could be a node object.
Here’s an update to my original example code that shows how to successfully filter using node attributes.
tree_view = slicer.qMRMLSubjectHierarchyTreeView()
tree_view.setMRMLScene(slicer.mrmlScene)
line_node_1 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsLineNode", "Line1")
line_node_2 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsLineNode", "Line2")
line_node_1.SetAttribute("Name", "Value")
tree_view.displayedItemCount() # 2
tree_view.addNodeAttributeFilter("Name", "Value")
tree_view.displayedItemCount() # 1
tree_view.removeNodeAttributeFilter("Name", "Value")
tree_view.displayedItemCount() # 2
1 Like
jcfr
(Jean Christophe Fillion Robin (Kitware))
October 31, 2023, 2:10am
8
The following should help clarify
Create the tree view
tree_view = slicer.qMRMLSubjectHierarchyTreeView()
tree_view.setMRMLScene(slicer.mrmlScene)
line_node_1 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsLineNode", "Line1")
line_node_2 = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsLineNode", "Line2")
assert tree_view.displayedItemCount() == 2
Set the node attribute
Adding a node attribute is different from a subject hierarchy item attribute
line_node_1.SetAttribute("Name", "Value")
Add a subject hierarchy item attribute fltter
tree_view.addItemAttributeFilter("Name", "Value")
… and as “expected” there are no item displayed
assert tree_view.displayedItemCount() == 0
Set the subject hierarchy item attribute
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
shItem1 = shNode.GetItemByDataNode(line_node_1)
shItem2 = shNode.GetItemByDataNode(line_node_2)
shNode.SetItemAttribute(shItem1, "Name", "Value")
# After setting the corrresponding item attribute, the items are filtered as expected
assert tree_view.displayedItemCount() == 1
1 Like
jcfr
(Jean Christophe Fillion Robin (Kitware))
November 15, 2023, 6:14pm
9
For reference, deprecation warnings and improved documentation are being integrated through the following pull request:
Slicer:main
← cpinter:sh-attribute-filter-deprecation
opened 01:31PM - 30 Oct 23 UTC
Re https://discourse.slicer.org/t/problems-filtering-subject-hierarchy-tree/3243… 5/5