unifying interface
same same for same functionality, allow user to specify single integer for convenience
This commit is contained in:
parent
53fe11484d
commit
f9e04bc4cb
|
@ -4,7 +4,7 @@ import warnings
|
|||
import multiprocessing as mp
|
||||
from functools import partial
|
||||
import typing
|
||||
from typing import Union, Optional, TextIO, List, Sequence
|
||||
from typing import Union, Optional, TextIO, List, Sequence, Collection
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
@ -17,7 +17,7 @@ from . import util
|
|||
from . import grid_filters
|
||||
from . import Rotation
|
||||
from . import Table
|
||||
from ._typehints import FloatSequence, IntSequence
|
||||
from ._typehints import FloatSequence, IntSequence, IntCollection
|
||||
|
||||
class Grid:
|
||||
"""
|
||||
|
@ -907,7 +907,8 @@ class Grid:
|
|||
|
||||
def clean(self,
|
||||
stencil: int = 3,
|
||||
mutable: IntSequence = None,
|
||||
selection: IntCollection = None,
|
||||
invert_selection: bool = False,
|
||||
periodic: bool = True) -> 'Grid':
|
||||
"""
|
||||
Smooth grid by selecting most frequent material ID within given stencil at each location.
|
||||
|
@ -916,8 +917,10 @@ class Grid:
|
|||
----------
|
||||
stencil : int, optional
|
||||
Size of smoothing stencil. Defaults to 3.
|
||||
mutable : sequence of int, optional
|
||||
Material ID that can be altered. Defaults to all.
|
||||
selection : int or collection of int, optional
|
||||
Material IDs to consider.
|
||||
invert_selection : bool, optional
|
||||
Consider all material IDs except those in selection. Defaults to False.
|
||||
periodic : bool, optional
|
||||
Assume grid to be periodic. Defaults to True.
|
||||
|
||||
|
@ -927,21 +930,23 @@ class Grid:
|
|||
Updated grid-based geometry.
|
||||
|
||||
"""
|
||||
def mostFrequent(arr: np.ndarray, mutable = None):
|
||||
def mostFrequent(arr: np.ndarray, selection: List, invert: bool):
|
||||
me = arr[arr.size//2]
|
||||
if selection is None or me in mutable:
|
||||
if len(selection) == 0 or np.isin(me,selection,invert=invert):
|
||||
unique, inverse = np.unique(arr,return_inverse=True)
|
||||
return unique[np.argmax(np.bincount(inverse))]
|
||||
else:
|
||||
return me
|
||||
|
||||
return Grid(material = ndimage.filters.generic_filter(
|
||||
extra_keywords = dict(selection=util.tbd(selection),invert=invert_selection)
|
||||
material = ndimage.filters.generic_filter(
|
||||
self.material,
|
||||
mostFrequent,
|
||||
size=(stencil if mutable is None else stencil//2*2+1,)*3,
|
||||
size=(stencil if selection is None else stencil//2*2+1,)*3,
|
||||
mode=('wrap' if periodic else 'nearest'),
|
||||
extra_keywords=dict(mutable=mutable),
|
||||
).astype(self.material.dtype),
|
||||
extra_keywords=extra_keywords,
|
||||
).astype(self.material.dtype)
|
||||
return Grid(material = material,
|
||||
size = self.size,
|
||||
origin = self.origin,
|
||||
comments = self.comments+[util.execution_stamp('Grid','clean')],
|
||||
|
@ -1061,16 +1066,16 @@ class Grid:
|
|||
|
||||
|
||||
def substitute(self,
|
||||
from_material: IntSequence,
|
||||
to_material: IntSequence) -> 'Grid':
|
||||
from_material: Union[int,IntSequence],
|
||||
to_material: Union[int,IntSequence]) -> 'Grid':
|
||||
"""
|
||||
Substitute material indices.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
from_material : sequence of int
|
||||
from_material : int or sequence of int
|
||||
Material indices to be substituted.
|
||||
to_material : sequence of int
|
||||
to_material : int or sequence of int
|
||||
New material indices.
|
||||
|
||||
Returns
|
||||
|
@ -1080,7 +1085,8 @@ class Grid:
|
|||
|
||||
"""
|
||||
material = self.material.copy()
|
||||
for f,t in zip(from_material,to_material): # ToDo Python 3.10 has strict mode for zip
|
||||
for f,t in zip(from_material if isinstance(from_material,(Sequence,np.ndarray)) else [from_material],
|
||||
to_material if isinstance(to_material,(Sequence,np.ndarray)) else [to_material]): # ToDo Python 3.10 has strict mode for zip
|
||||
material[self.material==f] = t
|
||||
|
||||
return Grid(material = material,
|
||||
|
@ -1115,14 +1121,14 @@ class Grid:
|
|||
def vicinity_offset(self,
|
||||
vicinity: int = 1,
|
||||
offset: int = None,
|
||||
trigger: IntSequence = [],
|
||||
selection: IntCollection = None,
|
||||
invert_selection: bool = False,
|
||||
periodic: bool = True) -> 'Grid':
|
||||
"""
|
||||
Offset material ID of points in the vicinity of xxx.
|
||||
|
||||
Different from themselves (or listed as triggers) within a given (cubic) vicinity,
|
||||
i.e. within the region close to a grain/phase boundary.
|
||||
ToDo: use include/exclude as in seeds.from_grid
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -1132,9 +1138,10 @@ class Grid:
|
|||
offset : int, optional
|
||||
Offset (positive or negative) to tag material indices,
|
||||
defaults to material.max()+1.
|
||||
trigger : sequence of int, optional
|
||||
List of material indices that trigger a change.
|
||||
Defaults to [], meaning that any different neighbor triggers a change.
|
||||
selection : int or collection of int, optional
|
||||
Material IDs to that triger xxx.
|
||||
invert_selection : bool, optional
|
||||
Consider all material IDs except those in selection. Defaults to False.
|
||||
periodic : bool, optional
|
||||
Assume grid to be periodic. Defaults to True.
|
||||
|
||||
|
@ -1144,17 +1151,19 @@ class Grid:
|
|||
Updated grid-based geometry.
|
||||
|
||||
"""
|
||||
def tainted_neighborhood(stencil: np.ndarray, trigger):
|
||||
def tainted_neighborhood(stencil: np.ndarray, selection):
|
||||
me = stencil[stencil.shape[0]//2]
|
||||
return np.any(stencil != me if len(trigger) == 0 else
|
||||
np.in1d(stencil,np.array(list(set(trigger) - {me}))))
|
||||
return np.any(stencil != me if len(selection) == 0 else
|
||||
np.in1d(stencil,np.array(list(set(selection) - {me}))))
|
||||
|
||||
offset_ = np.nanmax(self.material)+1 if offset is None else offset
|
||||
selection_ = util.tbd(selection) if not invert_selection else \
|
||||
list(set(self.material) - set(util.tbd(selection)))
|
||||
mask = ndimage.filters.generic_filter(self.material,
|
||||
tainted_neighborhood,
|
||||
size=1+2*vicinity,
|
||||
mode='wrap' if periodic else 'nearest',
|
||||
extra_keywords={'trigger':trigger})
|
||||
extra_keywords=dict(selection=selection_))
|
||||
|
||||
return Grid(material = np.where(mask, self.material + offset_,self.material),
|
||||
size = self.size,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Functionality for typehints."""
|
||||
|
||||
from typing import Sequence, Union, Literal, TextIO
|
||||
from typing import Sequence, Union, Literal, TextIO, Collection
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
@ -8,6 +8,7 @@ import numpy as np
|
|||
|
||||
FloatSequence = Union[np.ndarray,Sequence[float]]
|
||||
IntSequence = Union[np.ndarray,Sequence[int]]
|
||||
IntCollection = Union[np.ndarray,Collection[int]]
|
||||
FileHandle = Union[TextIO, str, Path]
|
||||
CrystalFamily = Union[None,Literal['triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic']]
|
||||
CrystalLattice = Union[None,Literal['aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF']]
|
||||
|
|
|
@ -6,7 +6,8 @@ from typing import Tuple as _Tuple
|
|||
from scipy import spatial as _spatial
|
||||
import numpy as _np
|
||||
|
||||
from ._typehints import FloatSequence as _FloatSequence, IntSequence as _IntSequence, NumpyRngSeed as _NumpyRngSeed
|
||||
from ._typehints import FloatSequence as _FloatSequence, IntSequence as _IntSequence, \
|
||||
NumpyRngSeed as _NumpyRngSeed, IntCollection as _IntCollection
|
||||
from . import util as _util
|
||||
from . import grid_filters as _grid_filters
|
||||
|
||||
|
@ -106,7 +107,7 @@ def from_Poisson_disc(size: _FloatSequence,
|
|||
|
||||
|
||||
def from_grid(grid,
|
||||
selection: _IntSequence = None,
|
||||
selection: _IntCollection = None,
|
||||
invert_selection: bool = False,
|
||||
average: bool = False,
|
||||
periodic: bool = True) -> _Tuple[_np.ndarray, _np.ndarray]:
|
||||
|
@ -117,7 +118,7 @@ def from_grid(grid,
|
|||
----------
|
||||
grid : damask.Grid
|
||||
Grid from which the material IDs are used as seeds.
|
||||
selection : sequence of int, optional
|
||||
selection : int or collection of int, optional
|
||||
Material IDs to consider.
|
||||
invert_selection : bool, optional
|
||||
Consider all material IDs except those in selection. Defaults to False.
|
||||
|
@ -133,8 +134,9 @@ def from_grid(grid,
|
|||
|
||||
"""
|
||||
material = grid.material.reshape((-1,1),order='F')
|
||||
mask = _np.full(grid.cells.prod(),True,dtype=bool) if selection is None else \
|
||||
_np.isin(material,selection,invert=invert_selection).flatten()
|
||||
selection_ = _util.tbd(selection)
|
||||
mask = _np.full(grid.cells.prod(),True,dtype=bool) if len(selection_) == 0 else \
|
||||
_np.isin(material,selection_,invert=invert_selection).flatten()
|
||||
coords = _grid_filters.coordinates0_point(grid.cells,grid.size).reshape(-1,3,order='F')
|
||||
|
||||
if not average:
|
||||
|
|
|
@ -9,7 +9,7 @@ import re
|
|||
import fractions
|
||||
from collections import abc
|
||||
from functools import reduce
|
||||
from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal
|
||||
from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, Collection
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
@ -720,7 +720,13 @@ def dict_flatten(d: Dict) -> Dict:
|
|||
|
||||
return new
|
||||
|
||||
|
||||
def tbd(arg) -> List:
|
||||
if arg is None:
|
||||
return []
|
||||
elif isinstance(arg,(np.ndarray,Collection)):
|
||||
return list(arg)
|
||||
else:
|
||||
return [arg]
|
||||
|
||||
####################################################################################################
|
||||
# Classes
|
||||
|
|
|
@ -155,7 +155,7 @@ class TestGrid:
|
|||
@pytest.mark.parametrize('selection',[None,[1],[1,2,3]])
|
||||
@pytest.mark.parametrize('periodic',[True,False])
|
||||
def test_clean(self,default,update,ref_path,stencil,selection,periodic):
|
||||
current = default.clean(stencil,selection,periodic)
|
||||
current = default.clean(stencil,selection,periodic=periodic)
|
||||
reference = ref_path/f'clean_{stencil}_{"+".join(map(str,[None] if selection is None else selection))}_{periodic}.vti'
|
||||
if update and stencil > 1:
|
||||
current.save(reference)
|
||||
|
@ -296,8 +296,8 @@ class TestGrid:
|
|||
assert grid_equal(G_1,G_2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('trigger',[[1],[]])
|
||||
def test_vicinity_offset(self,trigger):
|
||||
@pytest.mark.parametrize('selection',[[1],[]])
|
||||
def test_vicinity_offset(self,selection):
|
||||
offset = np.random.randint(2,4)
|
||||
vicinity = np.random.randint(2,4)
|
||||
|
||||
|
@ -309,17 +309,17 @@ class TestGrid:
|
|||
for i in [0,1,2]:
|
||||
m2[(np.roll(m,+vicinity,i)-m)!=0] += offset
|
||||
m2[(np.roll(m,-vicinity,i)-m)!=0] += offset
|
||||
if len(trigger) > 0:
|
||||
if len(selection) > 0:
|
||||
m2[m==1] = 1
|
||||
|
||||
grid = Grid(m,np.random.rand(3)).vicinity_offset(vicinity,offset,trigger=trigger)
|
||||
grid = Grid(m,np.random.rand(3)).vicinity_offset(vicinity,offset,selection=selection)
|
||||
|
||||
assert np.all(m2==grid.material)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('periodic',[True,False])
|
||||
def test_vicinity_offset_invariant(self,default,periodic):
|
||||
offset = default.vicinity_offset(trigger=[default.material.max()+1,
|
||||
offset = default.vicinity_offset(selection=[default.material.max()+1,
|
||||
default.material.min()-1])
|
||||
assert np.all(offset.material==default.material)
|
||||
|
||||
|
|
Loading…
Reference in New Issue