Skip to content

feat: add FillPartialArrays vtk filter and paraview plugin #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 65 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
a8a6b9b
add the VTK filter to fill a partial arrays of an input mesh
RomainBaville Jun 16, 2025
e948b20
add the pv plugin to fill a partial arrays of an input mesh
RomainBaville Jun 16, 2025
35c3b74
fixing bugs by removing the use of the abstract class
RomainBaville Jun 16, 2025
5633fce
Add the possibility to choose the atribute to fill without using para…
RomainBaville Jun 16, 2025
919351b
add the possibility to choose the value to fill in the function fillp…
RomainBaville Jun 18, 2025
10bfc75
add the possibility to choose a value to fill the partial array
RomainBaville Jun 18, 2025
839821a
add the possibility to choose the value to fill in the partial attrib…
RomainBaville Jun 18, 2025
870fb25
Update the documentation and upgrade the name of the variables
RomainBaville Jun 18, 2025
798003c
Small modifications of the doc
RomainBaville Jun 20, 2025
1494d5b
Update of the tests
RomainBaville Jun 20, 2025
f69e853
Formatting for the ci
RomainBaville Jun 20, 2025
4d9468e
Add the test file for the FillPartialArrays vtk filter
RomainBaville Jun 20, 2025
d44bc56
Formatting for the ci
RomainBaville Jun 20, 2025
5e0da35
Fix the lin with yapft issue
RomainBaville Jun 20, 2025
0ca3fe5
add a function to get the type of a vtk array
RomainBaville Jun 24, 2025
a905450
uptade the function createAttribute to preserve the type of the vtk a…
RomainBaville Jun 24, 2025
ec0302f
update the typing in the test of the function createAttribute
RomainBaville Jun 24, 2025
6c501c4
Update createAttribute and createConstantAttributeDataSet
RomainBaville Jun 26, 2025
510887d
Merge branch 'main' into RomainBaville/feature/update_geos-mesh_utils
RomainBaville Jun 26, 2025
490135c
update fillPartialAttribute and fillAllPartialAttributes
RomainBaville Jun 27, 2025
15a67fa
Add a function to get the vtk data type of an attribute of a multiblo…
RomainBaville Jun 27, 2025
5b17644
Formating for the CI
RomainBaville Jun 27, 2025
bd63003
Uptade functions calling utils functions
RomainBaville Jun 30, 2025
19ffa8d
Fix the doc issue
RomainBaville Jun 30, 2025
a0a2092
Apply suggestions from code review
RomainBaville Jun 30, 2025
bcdf4bd
Apply suggestions from code review
RomainBaville Jul 15, 2025
e79f5ab
Generalize error message of copyAttribute
RomainBaville Jul 15, 2025
b17e2e5
Add a raise assertion error in case of the mesh doen't have the attri…
RomainBaville Jul 15, 2025
5941980
Update the default value for uint case for fillpartialattribute
RomainBaville Jul 15, 2025
f46fde5
Cleen and add logger to manadge output messages
RomainBaville Jul 16, 2025
614cafa
clear the tests and functions of arrayModifiers
RomainBaville Jul 18, 2025
1423482
Clean fillpartialattribute and its test
RomainBaville Jul 22, 2025
0e2ded2
clean the code and add a funtion to test if an attribute is partial.
RomainBaville Jul 22, 2025
68d6c3c
fix the test of isAttributeGlobal
RomainBaville Jul 22, 2025
57c9bd2
Clean the code
RomainBaville Jul 22, 2025
b4ff24e
Clean for ci
RomainBaville Jul 23, 2025
7da8f9b
Clean for the ci
RomainBaville Jul 23, 2025
3c8f5d6
Clean doc
RomainBaville Jul 23, 2025
1d16852
Clean for ci
RomainBaville Jul 23, 2025
b4e2084
Clean For ci
RomainBaville Jul 23, 2025
f052c14
Clean For ci
RomainBaville Jul 23, 2025
6fc4f5d
Apply suggestions from Paloma's code review
RomainBaville Jul 28, 2025
80c08ae
Merge branch 'main' into RomainBaville/feature/update_geos-mesh_utils
RomainBaville Jul 28, 2025
36d715f
fix error in transferAttributes
RomainBaville Jul 28, 2025
8f0ed47
Merge branch 'main' into RomainBaville/feature/update_geos-mesh_utils
RomainBaville Jul 30, 2025
aa989f3
Merge branch 'main' into RomainBaville/feature/VTK_filter_and_PVFillP…
RomainBaville Jul 30, 2025
96f2237
Clean variables name and typing
RomainBaville Aug 6, 2025
2ac03bc
Remove the AsDF function
RomainBaville Aug 6, 2025
a021fa7
Change variables iter to iterator
RomainBaville Aug 6, 2025
f100bb8
Clean for ci
RomainBaville Aug 6, 2025
aeba4ba
move the plugin file in the new folder
RomainBaville Aug 6, 2025
065d6ff
update the doc with the new filter
RomainBaville Aug 6, 2025
20120be
remove the use of VTKPythonAlgorythmBase
RomainBaville Aug 6, 2025
9ca8b56
Fix logger
RomainBaville Aug 6, 2025
eb29afd
Update to the last version of the geos-mesh utils function
RomainBaville Aug 6, 2025
68b29f5
update FillPartialArrays to deals with multiple component
RomainBaville Aug 6, 2025
729f58c
update do set a value per component
RomainBaville Aug 6, 2025
b51bff1
Update to main
RomainBaville Aug 7, 2025
d23fc0a
clean for ci
RomainBaville Aug 7, 2025
0709b43
Update the test and the typing
RomainBaville Aug 7, 2025
e58f957
Apply suggestions from Paloma's code review
RomainBaville Aug 12, 2025
28bf48b
Update to main
RomainBaville Aug 12, 2025
71535ed
Update the doc
RomainBaville Aug 12, 2025
94219b3
Apply suggestions from Paloma's review
RomainBaville Aug 12, 2025
b58ded3
Fix doc
RomainBaville Aug 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/geos_mesh_docs/processing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ Processing filters
The `processing` module of `geos-mesh` package contains filters to process meshes.


geos.mesh.processing.FillPartialArrays filter
----------------------------------------------

.. automodule:: geos.mesh.processing.FillPartialArrays
:members:
:undoc-members:
:show-inheritance:


geos.mesh.processing.meshQualityMetricHelpers module
-----------------------------------------------------

Expand All @@ -12,6 +21,7 @@ geos.mesh.processing.meshQualityMetricHelpers module
:undoc-members:
:show-inheritance:


geos.mesh.processing.SplitMesh filter
--------------------------------------

Expand Down
5 changes: 5 additions & 0 deletions docs/geos_pv_docs/processing.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Post-/Pre-processing
=========================

PVFillPartialArrays
--------------------
.. automodule:: geos.pv.plugins.PVFillPartialArrays


PVSplitMesh
----------------------------------

Expand Down
154 changes: 154 additions & 0 deletions geos-mesh/src/geos/mesh/processing/FillPartialArrays.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Romain Baville, Martin Lemay

from typing_extensions import Self
from typing import Union, Any

from geos.utils.Logger import logging, Logger, getLogger
from geos.mesh.utils.arrayModifiers import fillPartialAttributes
from geos.mesh.utils.arrayHelpers import isAttributeInObject

from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet

__doc__ = """
Fill partial attributes of the input mesh with constant values per component.

Input mesh is vtkMultiBlockDataSet and attributes to fill must be partial.

The list of filling values per attribute is given by a dictionary.
Its keys are the attribute names and its items are the list of filling values for each component.

If the list of filling value is None, attributes are filled with the same constant value for each component;
0 for uint data, -1 for int data and nan for float data.

To use a handler of yours for the logger, set the variable 'speHandler' to True and add it to the filter
with the member function addLoggerHandler.

To use it:

.. code-block:: python

from geos.mesh.processing.FillPartialArrays import FillPartialArrays

# Filter inputs.
multiBlockDataSet: vtkMultiBlockDataSet
dictAttributesValues: dict[ str, Union[ list[ Any ], None ] ]
# Optional inputs.
speHandler: bool

# Instantiate the filter.
filter: FillPartialArrays = FillPartialArrays( multiBlockDataSet, dictAttributesValues, speHandler )

# Set the handler of yours (only if speHandler is True).
yourHandler: logging.Handler
filter.addLoggerHandler( yourHandler )

# Do calculations.
filter.applyFilter()
"""

loggerTitle: str = "Fill Partial Attribute"


class FillPartialArrays:

def __init__(
self: Self,
multiBlockDataSet: vtkMultiBlockDataSet,
dictAttributesValues: dict[ str, Union[ list[ Any ], None ] ],
speHandler: bool = False,
) -> None:
"""Fill partial attributes with constant value per component.

If the list of filling values for an attribute is None, it will filled with the default value for each component:
0 for uint data.
-1 for int data.
nan for float data.

Args:
multiBlockDataSet (vtkMultiBlockDataSet): The mesh where to fill the attribute.
dictAttributesValues (dict[str, Any]): The dictionary with the attribute to fill as keys and the list of filling values as items.
speHandler (bool, optional): True to use a specific handler, False to use the internal handler.
Defaults to False.
"""
self.multiBlockDataSet: vtkMultiBlockDataSet = multiBlockDataSet
self.dictAttributesValues: dict[ str, Union[ list[ Any ], None ] ] = dictAttributesValues

# Logger.
self.logger: Logger
if not speHandler:
self.logger = getLogger( loggerTitle, True )
else:
self.logger = logging.getLogger( loggerTitle )
self.logger.setLevel( logging.INFO )

def setLoggerHandler( self: Self, handler: logging.Handler ) -> None:
"""Set a specific handler for the filter logger.

In this filter 4 log levels are use, .info, .error, .warning and .critical, be sure to have at least the same 4 levels.

Args:
handler (logging.Handler): The handler to add.
"""
if not self.logger.hasHandlers():
self.logger.addHandler( handler )
else:
self.logger.warning(
"The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization."
)

def applyFilter( self: Self ) -> bool:
"""Create a constant attribute per region in the mesh.

Returns:
boolean (bool): True if calculation successfully ended, False otherwise.
"""
self.logger.info( f"Apply filter { self.logger.name }." )

for attributeName in self.dictAttributesValues:
self._setPieceRegionAttribute( attributeName )
if self.onPoints is None:
self.logger.error( f"{ attributeName } is not in the mesh." )
self.logger.error( f"The attribute { attributeName } has not been filled." )
self.logger.error( f"The filter { self.logger.name } failed." )
return False

if self.onBoth:
self.logger.error(
f"Their is two attribute named { attributeName }, one on points and the other on cells. The attribute must be unique."
)
self.logger.error( f"The attribute { attributeName } has not been filled." )
self.logger.error( f"The filter { self.logger.name } failed." )
return False

if not fillPartialAttributes( self.multiBlockDataSet,
attributeName,
onPoints=self.onPoints,
listValues=self.dictAttributesValues[ attributeName ],
logger=self.logger ):
self.logger.error( f"The filter { self.logger.name } failed." )
return False

self.logger.info( f"The filter { self.logger.name } succeed." )

return True

def _setPieceRegionAttribute( self: Self, attributeName: str ) -> None:
"""Set the attribute self.onPoints and self.onBoth.

self.onPoints is True if the region attribute is on points, False if it is on cells, None otherwise.

self.onBoth is True if a region attribute is on points and on cells, False otherwise.

Args:
attributeName (str): The name of the attribute to verify.
"""
self.onPoints: Union[ bool, None ] = None
self.onBoth: bool = False
if isAttributeInObject( self.multiBlockDataSet, attributeName, False ):
self.onPoints = False
if isAttributeInObject( self.multiBlockDataSet, attributeName, True ):
if self.onPoints is False:
self.onBoth = True
self.onPoints = True
52 changes: 33 additions & 19 deletions geos-mesh/src/geos/mesh/utils/arrayModifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
isAttributeGlobal,
getVtkArrayTypeInObject,
getVtkArrayTypeInMultiBlock,
getNumberOfComponentsMultiBlock,
)
from geos.mesh.utils.multiblockHelpers import (
getBlockElementIndexesFlatten,
Expand All @@ -61,18 +62,18 @@ def fillPartialAttributes(
multiBlockDataSet: Union[ vtkMultiBlockDataSet, vtkCompositeDataSet, vtkDataObject ],
attributeName: str,
onPoints: bool = False,
value: Any = np.nan,
listValues: Union[ list[ Any ], None ] = None,
logger: Union[ Logger, None ] = None,
) -> bool:
"""Fill input partial attribute of multiBlockDataSet with the same value for all the components.
"""Fill input partial attribute of multiBlockDataSet with a constant value per component.

Args:
multiBlockDataSet (vtkMultiBlockDataSet | vtkCompositeDataSet | vtkDataObject): MultiBlockDataSet where to fill the attribute.
attributeName (str): Attribute name.
onPoints (bool, optional): True if attributes are on points, False if they are on cells.
Defaults to False.
value (Any, optional): Filling value. It is recommended to use numpy scalar type for the values.
Defaults to:
listValues (list[Any], optional): List of filling value for each component.
Defaults to None, the filling value is for all components:
-1 for int VTK arrays.
0 for uint VTK arrays.
nan for float VTK arrays.
Expand All @@ -98,40 +99,53 @@ def fillPartialAttributes(

# Get information of the attribute to fill.
vtkDataType: int = getVtkArrayTypeInMultiBlock( multiBlockDataSet, attributeName, onPoints )
infoAttributes: dict[ str, int ] = getAttributesWithNumberOfComponents( multiBlockDataSet, onPoints )
nbComponents: int = infoAttributes[ attributeName ]
nbComponents: int = getNumberOfComponentsMultiBlock( multiBlockDataSet, attributeName, onPoints )
componentNames: tuple[ str, ...] = ()
if nbComponents > 1:
componentNames = getComponentNames( multiBlockDataSet, attributeName, onPoints )

typeMapping: dict[ int, type ] = vnp.get_vtk_to_numpy_typemap()
valueType: type = typeMapping[ vtkDataType ]
# Set the default value depending of the type of the attribute to fill
if np.isnan( value ):
typeMapping: dict[ int, type ] = vnp.get_vtk_to_numpy_typemap()
valueType: type = typeMapping[ vtkDataType ]
if listValues is None:
defaultValue: Any
logger.warning( f"The attribute { attributeName } is filled with the default value for each component." )
# Default value for float types is nan.
if vtkDataType in ( VTK_FLOAT, VTK_DOUBLE ):
value = valueType( value )
defaultValue = valueType( np.nan )
logger.warning(
f"{ attributeName } vtk data type is { vtkDataType } corresponding to { value.dtype } numpy type, default value is automatically set to nan."
f"{ attributeName } vtk data type is { vtkDataType } corresponding to { defaultValue.dtype } numpy type, default value is automatically set to nan."
)
# Default value for int types is -1.
elif vtkDataType in ( VTK_CHAR, VTK_SIGNED_CHAR, VTK_SHORT, VTK_LONG, VTK_INT, VTK_LONG_LONG, VTK_ID_TYPE ):
value = valueType( -1 )
defaultValue = valueType( -1 )
logger.warning(
f"{ attributeName } vtk data type is { vtkDataType } corresponding to { value.dtype } numpy type, default value is automatically set to -1."
f"{ attributeName } vtk data type is { vtkDataType } corresponding to { defaultValue.dtype } numpy type, default value is automatically set to -1."
)
# Default value for uint types is 0.
elif vtkDataType in ( VTK_BIT, VTK_UNSIGNED_CHAR, VTK_UNSIGNED_SHORT, VTK_UNSIGNED_LONG, VTK_UNSIGNED_INT,
VTK_UNSIGNED_LONG_LONG ):
value = valueType( 0 )
defaultValue = valueType( 0 )
logger.warning(
f"{ attributeName } vtk data type is { vtkDataType } corresponding to { value.dtype } numpy type, default value is automatically set to 0."
f"{ attributeName } vtk data type is { vtkDataType } corresponding to { defaultValue.dtype } numpy type, default value is automatically set to 0."
)
else:
logger.error( f"The type of the attribute { attributeName } is not compatible with the function." )
return False

values: list[ Any ] = [ value for _ in range( nbComponents ) ]
listValues = [ defaultValue ] * nbComponents

else:
if len( listValues ) != nbComponents:
return False

for idValue in range( nbComponents ):
value: Any = listValues[ idValue ]
if type( value ) is not valueType:
listValues[ idValue ] = valueType( listValues[ idValue ] )
logger.warning(
f"The filling value { value } for the attribute { attributeName } has not the correct type, it is convert to the numpy scalar type { valueType().dtype }."
)

# Parse the multiBlockDataSet to create and fill the attribute on blocks where it is not.
iterator: vtkDataObjectTreeIterator = vtkDataObjectTreeIterator()
Expand All @@ -141,7 +155,7 @@ def fillPartialAttributes(
while iterator.GetCurrentDataObject() is not None:
dataSet: vtkDataSet = vtkDataSet.SafeDownCast( iterator.GetCurrentDataObject() )
if not isAttributeInObjectDataSet( dataSet, attributeName, onPoints ) and \
not createConstantAttributeDataSet( dataSet, values, attributeName, componentNames, onPoints, vtkDataType, logger ):
not createConstantAttributeDataSet( dataSet, listValues, attributeName, componentNames, onPoints, vtkDataType, logger ):
return False

iterator.GoToNextItem()
Expand Down Expand Up @@ -172,7 +186,7 @@ def fillAllPartialAttributes(
infoAttributes: dict[ str, int ] = getAttributesWithNumberOfComponents( multiBlockDataSet, onPoints )
for attributeName in infoAttributes:
if not isAttributeGlobal( multiBlockDataSet, attributeName, onPoints ) and \
not fillPartialAttributes( multiBlockDataSet, attributeName, onPoints, logger=logger ):
not fillPartialAttributes( multiBlockDataSet, attributeName, onPoints=onPoints, logger=logger ):
return False

return True
Expand Down Expand Up @@ -384,7 +398,7 @@ def createConstantAttributeDataSet(
if valueType in ( int, float ):
npType: type = type( np.array( listValues )[ 0 ] )
logger.warning(
f"During the creation of the constant attribute { attributeName }, values will be converted from { valueType } to { npType }."
f"During the creation of the constant attribute { attributeName }, values have been converted from { valueType } to { npType }."
)
logger.warning( "To avoid any issue with the conversion, please use directly numpy scalar type for the values" )
valueType = npType
Expand Down
53 changes: 53 additions & 0 deletions geos-mesh/tests/test_FillPartialArrays.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Romain Baville
# SPDX-License-Identifier: Apache 2.0
# ruff: noqa: E402 # disable Module level import not at top of file
# mypy: disable-error-code="operator"
import pytest

from typing import Any
from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet

from geos.mesh.processing.FillPartialArrays import FillPartialArrays


@pytest.mark.parametrize( "dictAttributesValues", [
( {
"PORO": None
} ),
( {
"PERM": None
} ),
( {
"PORO": None,
"PERM": None
} ),
( {
"PORO": [ 4 ]
} ),
( {
"PERM": [ 4, 4, 4 ]
} ),
( {
"PORO": [ 4 ],
"PERM": [ 4, 4, 4 ]
} ),
( {
"PORO": None,
"PERM": [ 4, 4, 4 ]
} ),
( {
"PORO": [ 4 ],
"PERM": None
} ),
] )
def test_FillPartialArrays(
dataSetTest: vtkMultiBlockDataSet,
dictAttributesValues: dict[ str, Any ],
) -> None:
"""Test FillPartialArrays vtk filter."""
multiBlockDataSet: vtkMultiBlockDataSet = dataSetTest( "multiblock" )

filter: FillPartialArrays = FillPartialArrays( multiBlockDataSet, dictAttributesValues )
assert filter.applyFilter()
Loading