Custom Threshold filter with dropdown selection

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