Custom Threshold filter with dropdown selection

Hello,

I want to create a custom threshold filter for my simulation analysis. The “Minimum” and “Maximum” values should not be set by the user but adapted automatically according to the geometry item selected from a dropdown menu.

I have attached a small example simulation file for which this threshold should be used. The threshold is applied to the “Tag” cell data. For example:

  • to filter “tDot” objects, Minimum = 100 Maximum = 110
  • to filter “tPolygon”, Minimum = 300 Maximum = 333
  • to filter “tBodyPanel”, Minimum = Maximum = 332
  • and so on …

There are many more Minima/Maxima ranges and it gets quite tedious to look them up from a table.

How can this be achieved? I tried the XML-Plugin approach but I could not get it running.

Simulation.vtu (1.2 MB)

Hi Raphael,

I have loaded your ‘Simulation.vtu’ file with ‘Tag’ cell data in PV, and can not find tDot, tPolygon etc. objects. Could you please explain more your question?

Best regards,
Pavel

Hello Pavel,

yes, of course. After loading Simulation.vtu in ParaView, you create a Threshold filter in the Pipeline Browser for the “Tag” scalar. Then, you change the Minimum/Maximum sliders to filter geometric objects of the simulation model. For example, if you set minimum=100 and maximum=110, you will see only dots. For minimum=300 and maximum=332 you only see polygons (use “Surface with Edges” view).

The simulation program implements a lot of different subclasses for the dots and polygons. E.g. wake points for minimum=maximum=110. The dropdown menu should adjust the minimum/maximum values automatically. This alleviates the threshold filtering because I dont have to remember nor look up the table with the different geometry tags.

I hope I could clarify the question.

Greetings
Raphael

Thanks for the clarification, I understand now what you want to achieve. I think it is possible to implement by using custom python filter plugin.
I need some time to try to provide a more detailed/useful answer.

Best Regards,
Pavel

Hi Raphael,

I prepared the following:

This is the custom Python plugin for applying threshold iteratively for selected ranges of Tag property from predefined list:

# -*- coding: utf-8 -*-
"""
Created on Tue Nov 30 20:45:00 2021

@author: Pavel Novikov
"""

"""This is the plugin for ParaView for thresholding the model with multiple
ranges simultaneously by using a list of predefined ranges for values
of property array.

Works for property of 'int' type.

Output object type: vtkUnstructuredGrid,
     not vtkMultiBlockDataSet (as for vtkMultiThreshold filter)
"""

from paraview.util.vtkAlgorithm import *
import vtk


# Predefined ranges for values of 'Tag' property
TAG_RANGES = {
    'Dots': (100, 110), # (min, max)
    'Wake points': (110, 110),
    'Polygons': (300, 332),
    'Body Panel': (332, 332),
    }

    
def createModifiedCallback(anobject):
    """Return a function needed for multiple tag selection"""
    import weakref
    weakref_obj = weakref.ref(anobject)
    anobject = None
    def _markmodified(*args, **kwars):
        o = weakref_obj()
        if o is not None:
            o.Modified()
    return _markmodified


@smproxy.filter(label="Threshold By Predefined Ranges")
@smproperty.input(name="Input")
@smdomain.datatype(dataTypes=["vtkUnstructuredGrid"],
                   composite_data_supported=False)
class PreserveInputTypeFilter(VTKPythonAlgorithmBase):
    def __init__(self):
        super().__init__(nInputPorts=1, nOutputPorts=1,
                         outputType="vtkUnstructuredGrid")
        
        # Predefined ranges for dynamic selection        
        from vtkmodules.vtkCommonCore import vtkDataArraySelection
        self._arrayselection = vtkDataArraySelection()
        self._arrayselection.AddObserver("ModifiedEvent",
                                         createModifiedCallback(self))
        [self._arrayselection.AddArray(k) for k in TAG_RANGES.keys()]
        self._arrayselection.DisableAllArrays()
        
        self._tag_val_minmax = None # (min_val, max_val)
        self._tag_ranges_selected = {}

        
    def _get_array_selection(self):
        return self._arrayselection
        
    
    # Ability for users to choose in GUI which predefined ranges to use 
    # for thresholding the model.    
    @smproperty.dataarrayselection(name="Tag types")
    def GetDataArraySelection(self):
        return self._get_array_selection()
    
    
    def _value_in_tag_ranges(self, v):
        
        for r in self._tag_ranges_selected.values():            
            if (v >= r[0]) and (v <= r[1]):
                return True
            
        return False
              
        
    def _get_ranges_to_exclude(self):
        
        boundaries = [self._tag_val_minmax[0], self._tag_val_minmax[1]]
        for r in self._tag_ranges_selected.values():
            boundaries.extend([r[0], r[1]])
        boundaries.sort()
        
        ranges_to_exclude = []        
        for i in range(len(boundaries)-1):
            v = 0.5*sum(boundaries[i:i+2])
            if not self._value_in_tag_ranges(v):
                ranges_to_exclude.append((boundaries[i], boundaries[i+1]))
                
        return ranges_to_exclude
                
        
    def RequestDataObject(self, request, inInfo, outInfo):
        
        inData = self.GetInputData(inInfo, 0, 0)
        
        assert inData is not None
        
        if(inData.GetCellData().GetNumberOfArrays()>0):
            if not inData.GetCellData().HasArray('Tag'):
                raise RuntimeError('There is no "Tag" property in the model')
            else:
                minmax = inData.GetCellData().GetArray('Tag').GetRange()
                self._tag_val_minmax = (int(minmax[0])-1, int(minmax[1])+1)
                
        outData = self.GetOutputData(outInfo, 0)
        
        if outData is None or (not outData.IsA(inData.GetClassName())):
            outData = inData.NewInstance()
            outInfo.GetInformationObject(0).Set(outData.DATA_OBJECT(), outData)
            
        return super().RequestDataObject(request, inInfo, outInfo)


    def RequestData(self, request, inInfo, outInfo):
        """This is a set of actions performed after clicking the Apply
        button in ParaView filter GUI.
        """       
        
        inData = self.GetInputData(inInfo, 0, 0)
        
        # Create dict with selected tag types and their value ranges
        self._tag_ranges_selected = {}
        for i in range(self._arrayselection.GetNumberOfArrays()):
            k = self._arrayselection.GetArrayName(i)
            if self._arrayselection.ArrayIsEnabled(k):
                self._tag_ranges_selected[k] = TAG_RANGES[k]
               
        # Find reversed set of ranges to apply them for threshold filter
        # iteratively       
        ranges_to_exclude = self._get_ranges_to_exclude()
        
        thr = vtk.vtkThreshold()
        thr.SetInputData(inData)
        for r in ranges_to_exclude:            
            if r[0]+1 <= r[1]-1:
                thr.ThresholdBetween(r[0]+1, r[1]-1)
                thr.SetInputArrayToProcess(0, 0, 0,
                                           vtk.vtkDataObject.
                                           FIELD_ASSOCIATION_CELLS, 'Tag')
                thr.InvertOn()
                thr.Update()
        
        outData = self.GetOutputData(outInfo, 0)
        outData.ShallowCopy(thr.GetOutput())
        
        return 1

Hope this is the solution.

Best Regards,
Pavel

2 Likes

Hello Pavel,

thank you for this solution approach! Your implementation is an even better solution instead of the idea with the dropdown list because multiple categories can be filtered at the same time :smiley:

I think, there is maybe an issue with the _get_ranges_to_exclude because there are some objects visible although I have them unchecked (tested in another simulation file), but I will figure it out.

Can you recommend good resources to get started with programming these GUI filters or how did you learn it?

Thank you so much again!

Best Regards
Raphael