Skip to content

Commit 8081656

Browse files
refactor: Move the paraview plugin and create a VTK filter for CreateConstantAttributePerRegion (#122)
* Create the vtk filter from the paraview plugin * Move plugin to geos-pv * Add tests for the filter CreateConstantAttributePerRegion * Remove the use of VTKPythonAlgorithmBase in the filter CreateConstantAttributePerRegion * Improve the doc
1 parent 041441c commit 8081656

File tree

11 files changed

+1103
-339
lines changed

11 files changed

+1103
-339
lines changed

docs/geos_mesh_docs/processing.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ Processing filters
33

44
The `processing` module of `geos-mesh` package contains filters to process meshes.
55

6+
geos.mesh.processing.CreateConstantAttributePerRegion filter
7+
-------------------------------------------------------------
8+
9+
.. automodule:: geos.mesh.processing.CreateConstantAttributePerRegion
10+
:members:
11+
:undoc-members:
12+
:show-inheritance:
613

714
geos.mesh.processing.FillPartialArrays filter
815
----------------------------------------------

docs/geos_posp_docs/PVplugins.rst

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@ PVAttributeMapping plugin
1616
.. automodule:: PVplugins.PVAttributeMapping
1717

1818

19-
PVCreateConstantAttributePerRegion plugin
20-
---------------------------------------------------
21-
22-
.. automodule:: PVplugins.PVCreateConstantAttributePerRegion
23-
24-
2519
PVExtractMergeBlocksVolume plugin
2620
-------------------------------------------
2721

docs/geos_pv_docs/processing.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
Post-/Pre-processing
22
=========================
33

4+
5+
PVCreateConstantAttributePerRegion
6+
-----------------------------------
7+
8+
.. automodule:: geos.pv.plugins.PVCreateConstantAttributePerRegion
9+
10+
411
PVFillPartialArrays
512
--------------------
13+
614
.. automodule:: geos.pv.plugins.PVFillPartialArrays
715

816

geos-mesh/src/geos/mesh/processing/CreateConstantAttributePerRegion.py

Lines changed: 419 additions & 0 deletions
Large diffs are not rendered by default.

geos-mesh/src/geos/mesh/processing/FillPartialArrays.py

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from geos.utils.Logger import logging, Logger, getLogger
99
from geos.mesh.utils.arrayModifiers import fillPartialAttributes
10-
from geos.mesh.utils.arrayHelpers import isAttributeInObject
10+
from geos.mesh.utils.arrayHelpers import getAttributePieceInfo
1111

1212
from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet
1313

@@ -23,7 +23,7 @@
2323
0 for uint data, -1 for int data and nan for float data.
2424
2525
To use a handler of yours for the logger, set the variable 'speHandler' to True and add it to the filter
26-
with the member function addLoggerHandler.
26+
with the member function setLoggerHandler.
2727
2828
To use it:
2929
@@ -42,7 +42,7 @@
4242
4343
# Set the handler of yours (only if speHandler is True).
4444
yourHandler: logging.Handler
45-
filter.addLoggerHandler( yourHandler )
45+
filter.setLoggerHandler( yourHandler )
4646
4747
# Do calculations.
4848
filter.applyFilter()
@@ -106,15 +106,17 @@ def applyFilter( self: Self ) -> bool:
106106
"""
107107
self.logger.info( f"Apply filter { self.logger.name }." )
108108

109+
onPoints: Union[ None, bool ]
110+
onBoth: bool
109111
for attributeName in self.dictAttributesValues:
110-
self._setPieceRegionAttribute( attributeName )
111-
if self.onPoints is None:
112+
onPoints, onBoth = getAttributePieceInfo( self.multiBlockDataSet, attributeName )
113+
if onPoints is None:
112114
self.logger.error( f"{ attributeName } is not in the mesh." )
113115
self.logger.error( f"The attribute { attributeName } has not been filled." )
114116
self.logger.error( f"The filter { self.logger.name } failed." )
115117
return False
116118

117-
if self.onBoth:
119+
if onBoth:
118120
self.logger.error(
119121
f"Their is two attribute named { attributeName }, one on points and the other on cells. The attribute must be unique."
120122
)
@@ -124,7 +126,7 @@ def applyFilter( self: Self ) -> bool:
124126

125127
if not fillPartialAttributes( self.multiBlockDataSet,
126128
attributeName,
127-
onPoints=self.onPoints,
129+
onPoints=onPoints,
128130
listValues=self.dictAttributesValues[ attributeName ],
129131
logger=self.logger ):
130132
self.logger.error( f"The filter { self.logger.name } failed." )
@@ -133,22 +135,3 @@ def applyFilter( self: Self ) -> bool:
133135
self.logger.info( f"The filter { self.logger.name } succeed." )
134136

135137
return True
136-
137-
def _setPieceRegionAttribute( self: Self, attributeName: str ) -> None:
138-
"""Set the attribute self.onPoints and self.onBoth.
139-
140-
self.onPoints is True if the region attribute is on points, False if it is on cells, None otherwise.
141-
142-
self.onBoth is True if a region attribute is on points and on cells, False otherwise.
143-
144-
Args:
145-
attributeName (str): The name of the attribute to verify.
146-
"""
147-
self.onPoints: Union[ bool, None ] = None
148-
self.onBoth: bool = False
149-
if isAttributeInObject( self.multiBlockDataSet, attributeName, False ):
150-
self.onPoints = False
151-
if isAttributeInObject( self.multiBlockDataSet, attributeName, True ):
152-
if self.onPoints is False:
153-
self.onBoth = True
154-
self.onPoints = True

geos-mesh/src/geos/mesh/utils/arrayHelpers.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,102 @@ def has_array( mesh: vtkUnstructuredGrid, array_names: list[ str ] ) -> bool:
4747
return False
4848

4949

50+
def getAttributePieceInfo(
51+
mesh: Union[ vtkDataSet, vtkMultiBlockDataSet ],
52+
attributeName: str,
53+
) -> tuple[ Union[ None, bool ], bool ]:
54+
"""Get the attribute piece information.
55+
56+
Two information are given:
57+
- onPoints (Union[None, bool]): True if the attribute is on points or on both pieces, False if it is on cells, None otherwise.
58+
- onBoth (bool): True if the attribute is on points and on cells, False otherwise.
59+
60+
Args:
61+
mesh (Union[vtkDataSet, vtkMultiBlockDataSet]): The mesh with the attribute.
62+
attributeName (str): The name of the attribute.
63+
64+
Returns:
65+
tuple[Union[None, bool], bool]: The piece information of the attribute.
66+
"""
67+
onPoints: Union[ bool, None ] = None
68+
onBoth: bool = False
69+
if isAttributeInObject( mesh, attributeName, False ):
70+
onPoints = False
71+
if isAttributeInObject( mesh, attributeName, True ):
72+
if onPoints is False:
73+
onBoth = True
74+
onPoints = True
75+
76+
return ( onPoints, onBoth )
77+
78+
79+
def checkValidValuesInMultiBlock(
80+
multiBlockDataSet: vtkMultiBlockDataSet,
81+
attributeName: str,
82+
listValues: list[ Any ],
83+
onPoints: bool,
84+
) -> tuple[ list[ Any ], list[ Any ] ]:
85+
"""Check if each value is valid , ie if that value is a data of the attribute in at least one dataset of the multiblock.
86+
87+
Args:
88+
multiBlockDataSet (vtkMultiBlockDataSet): The multiblock dataset mesh to check.
89+
attributeName (str): The name of the attribute with the data.
90+
listValues (list[Any]): The list of values to check.
91+
onPoints (bool): True if the attribute is on points, False if on cells.
92+
93+
Returns:
94+
tuple[list[Any], list[Any]]: Tuple containing the list of valid values and the list of the invalid ones.
95+
"""
96+
validValues: list[ Any ] = []
97+
invalidValues: list[ Any ] = []
98+
listFlatIdDataSet: list[ int ] = getBlockElementIndexesFlatten( multiBlockDataSet )
99+
for flatIdDataSet in listFlatIdDataSet:
100+
dataSet: vtkDataSet = vtkDataSet.SafeDownCast( multiBlockDataSet.GetDataSet( flatIdDataSet ) )
101+
# Get the valid values of the dataset.
102+
validValuesDataSet: list[ Any ] = checkValidValuesInDataSet( dataSet, attributeName, listValues, onPoints )[ 0 ]
103+
104+
# Keep the new true values.
105+
for value in validValuesDataSet:
106+
if value not in validValues:
107+
validValues.append( value )
108+
109+
# Get the false indexes.
110+
for value in listValues:
111+
if value not in validValues:
112+
invalidValues.append( value )
113+
114+
return ( validValues, invalidValues )
115+
116+
117+
def checkValidValuesInDataSet(
118+
dataSet: vtkDataSet,
119+
attributeName: str,
120+
listValues: list[ Any ],
121+
onPoints: bool,
122+
) -> tuple[ list[ Any ], list[ Any ] ]:
123+
"""Check if each value is valid , ie if that value is a data of the attribute in the dataset.
124+
125+
Args:
126+
dataSet (vtkDataSet): The dataset mesh to check.
127+
attributeName (str): The name of the attribute with the data.
128+
listValues (list[Any]): The list of values to check.
129+
onPoints (bool): True if the attribute is on points, False if on cells.
130+
131+
Returns:
132+
tuple[list[Any], list[Any]]: Tuple containing the list of valid values and the list of the invalid ones.
133+
"""
134+
attributeNpArray = getArrayInObject( dataSet, attributeName, onPoints )
135+
validValues: list[ Any ] = []
136+
invalidValues: list[ Any ] = []
137+
for value in listValues:
138+
if value in attributeNpArray:
139+
validValues.append( value )
140+
else:
141+
invalidValues.append( value )
142+
143+
return ( validValues, invalidValues )
144+
145+
50146
def getFieldType( data: vtkFieldData ) -> str:
51147
"""Returns whether the data is "vtkFieldData", "vtkCellData" or "vtkPointData".
52148
@@ -357,6 +453,24 @@ def getArrayInObject( dataSet: vtkDataSet, attributeName: str, onPoints: bool )
357453
return npArray
358454

359455

456+
def getVtkDataTypeInObject( multiBlockDataSet: Union[ vtkDataSet, vtkMultiBlockDataSet ], attributeName: str,
457+
onPoints: bool ) -> int:
458+
"""Return VTK type of requested array from input mesh.
459+
460+
Args:
461+
multiBlockDataSet (Union[vtkDataSet, vtkMultiBlockDataSet]): Input multiBlockDataSet.
462+
attributeName (str): Name of the attribute.
463+
onPoints (bool): True if attributes are on points, False if they are on cells.
464+
465+
Returns:
466+
int: The type of the vtk array corresponding to input attribute name.
467+
"""
468+
if isinstance( multiBlockDataSet, vtkDataSet ):
469+
return getVtkArrayTypeInObject( multiBlockDataSet, attributeName, onPoints )
470+
else:
471+
return getVtkArrayTypeInMultiBlock( multiBlockDataSet, attributeName, onPoints )
472+
473+
360474
def getVtkArrayTypeInObject( dataSet: vtkDataSet, attributeName: str, onPoints: bool ) -> int:
361475
"""Return VTK type of requested array from dataset input.
362476
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
3+
# SPDX-FileContributor: Romain Baville
4+
# SPDX-License-Identifier: Apache 2.0
5+
# ruff: noqa: E402 # disable Module level import not at top of file
6+
# mypy: disable-error-code="operator"
7+
import pytest
8+
from typing import Union, Any
9+
from vtkmodules.vtkCommonDataModel import ( vtkDataSet, vtkMultiBlockDataSet )
10+
11+
from geos.mesh.processing.CreateConstantAttributePerRegion import CreateConstantAttributePerRegion, np
12+
13+
14+
@pytest.mark.parametrize(
15+
"meshType, newAttributeName, regionName, dictRegionValues, componentNames, componentNamesTest, valueNpType, succeed",
16+
[
17+
# Test the name of the new attribute (new on the mesh, one present on the other piece).
18+
## For vtkDataSet.
19+
( "dataset", "newAttribute", "GLOBAL_IDS_POINTS", {}, (), (), np.float32, True ),
20+
( "dataset", "CellAttribute", "GLOBAL_IDS_POINTS", {}, (), (), np.float32, True ),
21+
## For vtkMultiBlockDataSet.
22+
( "multiblock", "newAttribute", "GLOBAL_IDS_POINTS", {}, (), (), np.float32, True ),
23+
( "multiblock", "CellAttribute", "GLOBAL_IDS_POINTS", {}, (), (), np.float32, True ),
24+
( "multiblock", "GLOBAL_IDS_CELLS", "GLOBAL_IDS_POINTS", {}, (), (), np.float32, True ),
25+
# Test if the region attribute is on cells or on points.
26+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.float32, True ),
27+
# Test the component name.
28+
( "dataset", "newAttribute", "FAULT", {}, ( "X" ), (), np.float32, True ),
29+
( "dataset", "newAttribute", "FAULT", {}, (), ( "Component0", "Component1" ), np.float32, True ),
30+
( "dataset", "newAttribute", "FAULT", {}, ( "X" ), ( "Component0", "Component1" ), np.float32, True ),
31+
( "dataset", "newAttribute", "FAULT", {}, ( "X", "Y" ), ( "X", "Y" ), np.float32, True ),
32+
( "dataset", "newAttribute", "FAULT", {}, ( "X", "Y", "Z" ), ( "X", "Y" ), np.float32, True ),
33+
# Test the type of value.
34+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.int8, True ),
35+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.int16, True ),
36+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.int32, True ),
37+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.int64, True ),
38+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.uint8, True ),
39+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.uint16, True ),
40+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.uint32, True ),
41+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.uint64, True ),
42+
( "dataset", "newAttribute", "FAULT", {}, (), (), np.float64, True ),
43+
# Test index/value.
44+
( "dataset", "newAttribute", "FAULT", {
45+
0: [ 0 ],
46+
100: [ 1 ]
47+
}, (), (), np.float32, True ),
48+
( "dataset", "newAttribute", "FAULT", {
49+
0: [ 0 ],
50+
100: [ 1 ],
51+
101: [ 2 ]
52+
}, (), (), np.float32, True ),
53+
( "dataset", "newAttribute", "FAULT", {
54+
0: [ 0 ],
55+
100: [ 1 ],
56+
101: [ 2 ],
57+
2: [ 3 ]
58+
}, (), (), np.float32, True ),
59+
( "dataset", "newAttribute", "FAULT", {
60+
0: [ 0, 0 ],
61+
100: [ 1, 1 ]
62+
}, (), ( "Component0", "Component1" ), np.float32, True ),
63+
( "dataset", "newAttribute", "FAULT", {
64+
0: [ 0, 0 ],
65+
100: [ 1, 1 ],
66+
101: [ 2, 2 ]
67+
}, (), ( "Component0", "Component1" ), np.float32, True ),
68+
( "dataset", "newAttribute", "FAULT", {
69+
0: [ 0, 0 ],
70+
100: [ 1, 1 ],
71+
101: [ 2, 2 ],
72+
2: [ 3, 3 ]
73+
}, (), ( "Component0", "Component1" ), np.float32, True ),
74+
# Test common error.
75+
## Number of components.
76+
( "dataset", "newAttribute", "FAULT", {
77+
0: [ 0 ],
78+
100: [ 1, 1 ]
79+
}, (), (), np.float32, False ), # Number of value inconsistent.
80+
( "dataset", "newAttribute", "FAULT", {
81+
0: [ 0, 0 ],
82+
100: [ 1, 1 ]
83+
}, (), (), np.float32, False ), # More values than components.
84+
( "dataset", "newAttribute", "FAULT", {
85+
0: [ 0 ],
86+
100: [ 1 ]
87+
}, ( "X", "Y" ), ( "X", "Y" ), np.float32, False ), # More components than value.
88+
## Attribute name.
89+
( "dataset", "PERM", "FAULT", {}, (), (), np.float32, False ), # The attribute name already exist.
90+
## Region attribute.
91+
( "dataset", "newAttribute", "PERM", {}, (),
92+
(), np.float32, False ), # Region attribute has too many components.
93+
( "multiblock", "newAttribute", "FAULT", {}, (), (), np.float32, False ), # Region attribute is partial.
94+
] )
95+
def test_CreateConstantAttributePerRegion(
96+
dataSetTest: Union[ vtkMultiBlockDataSet, vtkDataSet ],
97+
meshType: str,
98+
newAttributeName: str,
99+
regionName: str,
100+
dictRegionValues: dict[ Any, Any ],
101+
componentNames: tuple[ str, ...],
102+
componentNamesTest: tuple[ str, ...],
103+
valueNpType: int,
104+
succeed: bool,
105+
) -> None:
106+
"""Test CreateConstantAttributePerRegion."""
107+
mesh: Union[ vtkMultiBlockDataSet, vtkDataSet ] = dataSetTest( meshType )
108+
nbComponents: int = len( componentNamesTest )
109+
if nbComponents == 0: # If the attribute has one component, the component has no name.
110+
nbComponents += 1
111+
112+
filter: CreateConstantAttributePerRegion = CreateConstantAttributePerRegion(
113+
mesh,
114+
regionName,
115+
dictRegionValues,
116+
newAttributeName,
117+
valueNpType=valueNpType,
118+
nbComponents=nbComponents,
119+
componentNames=componentNames,
120+
)
121+
122+
assert filter.applyFilter() == succeed

0 commit comments

Comments
 (0)