HyperTreeGrid in VTK: Using Masks

assortment of colored squares

This is the third part of a series of blog articles about vtkHyperTreeGrid usage and implementation in VTK. The first part, an introduction about HTG, can be found here, the second part, HTG data construction, can be found here, the fourth part, HTG: Specific Filters, can be found here, the fifth part, HTG: Cursors and Supercursors, can be found here.

After quickly focusing on the TB-AMR (tree-based Adaptive Mesh Refinement) technique and a HyperTreeGrid building (the TB-AMR representation in VTK), we suggest to extend this construction to include the notion of mask.

Declare and set mask

Let us resume the construction of this kind of representation by following the example written in Python from the previous article in order to complete it.

We keep the construction stages by modifying the third step from HTG data construction article, Use the cursor to build the tree with successive refinement.

As we have seen, at any time when browsing the datastructure, information is accessible from the cursor, in particular the global offset, which identifies the index in an array where the value of the current cell must be stored, or the access reading mode.

Once we have declared an array, it is then easy to position the value for the cell pointed to by the cursor by doing this:

idx = cursor.GetGlobalNodeIndex()
scalar.InsertTuple1(idx, val)
scalar.GetTuple1(idx, val)

The mask setting is done in a similar way by declaring an array of bits (the name is free):

maskArray = vtk.vtkBitArray()
maskArray.SetName('mask')
maskArray.SetNumberOfValues(0)

and then applied during construction:

idx = cursor.GetGlobalNodeIndex()
mask.InsertTuple1(idx, True)  # True or False

True means that the cell is hidden, therefore invisible.
False means that the cell is not hidden, therefore visible.

Associate mask on HTG

Once the construction of the HTG is finished, we send the mask to the latter by the following call:

htg.SetMask(maskArray)

Read access to the mask value using the cursor

During a read scan, do not forget to test the value of the mask by doing:

[...]
if not cursor.IsMasked():
    [...]

The mask is then automatically taken into account in the filters and render mappers.

Write access to the mask value using the cursor

During a write scan, the mask has not yet been positioned at the HyperTreeGrid, you must directly interrogate the mask array by doing:

[...]
idx = cursor.GetGlobalNodeIndex()
if not mask.GetValue(idx):
    [...]

This can happen because your way of building a HyperTree requires going over the same spot multiple times.

The parent mask status can override daughter mask status

If a parent cell is masked, the daughter cells will be considered as masked, even if the value of the mask is not True. The memory occupation does not change, nor does the number of vertices (coarse or fine/leaf) in the tree.

Set mask on child cells

After refining a cell, it is important that you browse each of the child cells in order to set the mask to False, the default value. A safe way to do this is to combine a refinement:

cursor.SubdivideLeaf()
for iCell in range(cursor.GetNumberOfChildren()):
    cursor.ToChild(iCell)
    idx = cursor.GetGlobalNodeIndex()
    mask.InsertTuple1(idx, False)
    cursor.ToParent()

This is especially important if you are browsing the tree not only in construction but also in reading. In fact, you might then find something like this in the code :

[...]
    if cursor.IsLeaf():
        cursor.SubdivideLeaf()
        for iCell in range(cursor.GetNumberOfChildren()):
            cursor.ToChild(iCell)
            idx = cursor.GetGlobalNodeIndex()
            mask.InsertTuple1(idx, False) # set default value
            cursor.ToParent()
    for iCell in range(cursor.GetNumberOfChildren()):
        cursor.ToChild(iCell)
        idx = cursor.GetGlobalNodeIndex()
        [...] # set a new value on mask or get a value on mask by mask.GetValue(idx)
        cursor.ToParent()
[...]

In the next example, the tree is traversed only once and does not require this coding “protection”.

Small complete example

We provide here a small complete Python code whose result will give:

Fig 1: The result of this example building an TB-AMR (HTG) of 3×2 cells of which three cells are defined of which has been refined, included a mask which was used to hide a leaf cell. Note that if you do not initialize with a cursor under construction on a rectilinear grid cell, the cell is not defined.
[...]
# Declare scalar and mask
[...]
maskArray = vtk.vtkBitArray()
maskArray.SetName('mask')
maskArray.SetNumberOfValues(0)
[...]
# Assigns an index for the value of the cell pointed to by the current cursor state 
idx = cursor.GetGlobalNodeIndex() # cell 0
scalarArray.InsertTuple1(idx, 1)
maskArray.InsertTuple1(idx, False)
# Decides to subdivide cell pointed by current cursor state
cursor.SubdivideLeaf()  # cell 0
# In this example, 4 child cells were constructed
# Move the cursor to the first child 
cursor.ToChild(0)  # cell 0/0
idx = cursor.GetGlobalNodeIndex()
scalarArray.InsertTuple1(idx, 7)
maskArray.InsertTuple1(idx, False)
cursor.ToParent()  # cell 0
# Move the cursor to the second child
cursor.ToChild(1)  # cell 0/1
idx = cursor.GetGlobalNodeIndex()
scalarArray.InsertTuple1(idx, 8)
maskArray.InsertTuple1(idx, False)
cursor.ToParent()  # cell 0
# And next
cursor.ToChild(2)  # cell 0/2
idx = cursor.GetGlobalNodeIndex()
scalarArray.InsertTuple1(idx, 9)
maskArray.InsertTuple1(idx, True)
cursor.ToParent()  # cell 0
# And next
cursor.ToChild(3)  # cell 0/3
idx = cursor.GetGlobalNodeIndex()
scalarArray.InsertTuple1(idx, 10)
maskArray.InsertTuple1(idx, False)
cursor.ToParent()  # cell 0
# I'm finish refined thie cell 0
offsetIndex += cursor.GetTree().GetNumberOfVertices()
# Set cursor on cell #3 and set the start index
htg.InitializeNonOrientedCursor(cursor, 1, True)
cursor.SetGlobalIndexStart(offsetIndex) # cell 1
idx = cursor.GetGlobalNodeIndex()
scalarArray.InsertTuple1(idx, 2)
maskArray.InsertTuple1(idx, False)
# I'm finish refined thie cell 1
offsetIndex += cursor.GetTree().GetNumberOfVertices()
# Set cursor on cell #3 and set the start index
htg.InitializeNonOrientedCursor(cursor, 3, True)
cursor.SetGlobalIndexStart(offsetIndex) # cell 3
idx = cursor.GetGlobalNodeIndex()
scalarArray.InsertTuple1(idx, 4)
maskArray.InsertTuple1(idx, False)
# I'm finish refined thie cell 3
offsetIndex += cursor.GetTree().GetNumberOfVertices()
# Set cursor on cell #5 and set the start index
htg.InitializeNonOrientedCursor(cursor, 5, True)
cursor.SetGlobalIndexStart(offsetIndex) # cell 5
idx = cursor.GetGlobalNodeIndex()
scalarArray.InsertTuple1(idx, 6)
maskArray.InsertTuple1(idx, False)
# I'm finish refined thie cell 5
offsetIndex += cursor.GetTree().GetNumberOfVertices()
# Which ends the construction of my mesh
# Set mask after created
htg.SetMask(maskArray)
# Used a Geometry filter
[...]

Code validated in ParaView 5.9.1

A complete example in Python was made by Sébastien Jourdain during a “Hackathon” in January 2019, but by preallocating the size of the mask in order to set it by default to false. https://gitlab.kitware.com/vtk/vtk/-/blob/master/Common/DataModel/Testing/Python/TestHyperTreeGrid2DMask.py

Other example in 3D: https://gitlab.kitware.com/vtk/vtk/-/blob/master/Common/DataModel/Testing/Python/TestHyperTreeGrid3DMandel.py

CEA, DAM, DIF, F-91297 Arpajon, France

Tags:

Leave a Reply