improving import from DREAM.3D

- no duplicated entries even for non-segmented data
- using phase labels from file
- material.yaml: Dummy homogenization and phase
- tests to ensure correct order and match between Grid and
  ConfigMaterial
This commit is contained in:
Martin Diehl 2021-03-20 12:51:41 +01:00
parent cdcedd0d44
commit 394fda5f37
4 changed files with 75 additions and 68 deletions

View File

@ -1,4 +1,4 @@
from os import path import os.path
import numpy as np import numpy as np
import h5py import h5py
@ -98,7 +98,10 @@ class ConfigMaterial(Config):
@staticmethod @staticmethod
def load_DREAM3D(fname,data_group,ori_data,phase_id,phase_name,base_group=None): def load_DREAM3D(fname,
grain_data=None,cell_data='CellData',cell_ensemble_data='CellEnsembleData',
phases='Phases',Euler_angles='EulerAngles',phase_names='PhaseName',
base_group=None):
""" """
Load material data from DREAM3D file. Load material data from DREAM3D file.
@ -107,70 +110,33 @@ class ConfigMaterial(Config):
Parameters Parameters
---------- ----------
fname : str fname : str
path to the DREAM3D file. Filename of the DREAM.3D (HDF5) file.
base_group : str base_group : str
Name of the group (folder) below 'DataContainers', Path to the group (folder) that contains the geometry (_SIMPL_GEOMETRY),
for example 'SyntheticVolumeDataContainer'. and, optionally, the cell data. Defaults to None, in which case
data_group : str it is set as the path that contains _SIMPL_GEOMETRY/SPACING.
Name of the group (folder) having relevant data for conversion,
for example 'Grain Data' or 'CellData'.
ori_data : str
Name of the dataset having orientation data (working with Euler Angles in dream3D file),
For example 'EulerAngles'.
phase_id : str
Name of the dataset containing phase IDs for each grain,
for example 'Phases'.
phase_name : list
List with name of the phases.
Examples
--------
for grain based data with single phase
>>> import damask
>>> import damask.ConfigMaterial as cm
>>> cm.load_from_Dream3D('20grains16x16x16.dream3D','SyntheticVolumeDataContainer', 'Grain Data',
... 'EulerAngles','Phases',['Ferrite'])
for point based data with single phase
>>> import damask
>>> import damask.ConfigMaterial as cm
>>> cm.load_from_Dream3D('20grains16x16x16.dream3D','SyntheticVolumeDataContainer', 'CellData',
... 'EulerAngles','Phases',['Ferrite'])
for grain based data with dual phase
>>> import damask
>>> import damask.ConfigMaterial as cm
>>> cm.load_from_Dream3D('20grains16x16x16.dream3D','SyntheticVolumeDataContainer', 'Grain Data',
... 'EulerAngles','Phases',['Ferrite','Martensite'])
for point based data with dual phase
>>> import damask
>>> import damask.ConfigMaterial as cm
>>> cm.load_from_Dream3D('20grains16x16x16.dream3D','SyntheticVolumeDataContainer', 'CellData',
... 'EulerAngles','Phases',['Ferrite','Martensite'])
""" """
b = util.DREAM3D_base_group(fname) if base_group is None else base_group b = util.DREAM3D_base_group(fname) if base_group is None else base_group
hdf = h5py.File(fname,'r') f = h5py.File(fname,'r')
orientation_path = path.join(b,data_group,ori_data) if grain_data is None:
if hdf[orientation_path].attrs['TupleDimensions'].shape == (3,): phase = f[os.path.join(b,cell_data,phases)][()].flatten()
grain_orientations = np.array(hdf[orientation_path]).reshape(-1,3,order='F') O = Rotation.from_Euler_angles(f[os.path.join(b,cell_data,Euler_angles)]).as_quaternion().reshape(-1,4) # noqa
_,idx = np.unique(np.hstack([O,phase.reshape(-1,1)]),return_index=True,axis=0)
idx = np.sort(idx)
else: else:
grain_orientations = np.array(hdf[orientation_path])[1:] phase = f[os.path.join(b,grain_data,phases)][()]
O = Rotation.from_Euler_angles(f[os.path.join(b,grain_data,Euler_angles)]).as_quaternion() # noqa
idx = np.arange(phase.size)
grain_quats = Rotation.from_Euler_angles(grain_orientations).as_quaternion() if cell_ensemble_data is not None:
names = np.array([s.decode() for s in f[os.path.join(b,cell_ensemble_data,phase_names)]])
phase = names[phase]
phase_path = path.join(b,data_group,phase_id) material = {k:np.atleast_1d(v[idx].squeeze()) for k,v in zip(['O','phase'],[O,phase])}
if hdf[phase_path].attrs['TupleDimensions'].shape == (3,): return ConfigMaterial({'phase':{k if isinstance(k,int) else str(k):'tbd' for k in np.unique(phase)},
grain_phase = np.array(hdf[phase_path]).reshape(-1,order='F') 'homogenization':{'direct':{'N_constituents':1}}}).material_add(**material)
else:
grain_phase = np.array(hdf[phase_path])[1:]
grain_phase = grain_phase.reshape(len(grain_phase),)
phase_name_list = [phase_name[i - 1] for i in grain_phase]
return ConfigMaterial().material_add(phase=phase_name_list, O = grain_quats) # noqa
@property @property

View File

@ -256,14 +256,17 @@ class Grid:
@staticmethod @staticmethod
def load_DREAM3D(fname,cell_data=None,material='FeatureIds',base_group=None): def load_DREAM3D(fname,
feature_IDs=None,cell_data='CellData',
phases='Phases',Euler_angles='EulerAngles',
base_group=None):
""" """
Load from DREAM.3D file. Load from DREAM.3D file.
Parameters Parameters
---------- ----------
fname : str fname : str
Filename of the DREAM.3D file Filename of the DREAM.3D (HDF5) file.
cell_data : str, optional cell_data : str, optional
Name of the group (folder) containing the pointwise material data, Name of the group (folder) containing the pointwise material data,
for example 'CellData'. Defaults to None, in which case points are consecutively numbered. for example 'CellData'. Defaults to None, in which case points are consecutively numbered.
@ -279,11 +282,17 @@ class Grid:
b = util.DREAM3D_base_group(fname) if base_group is None else base_group b = util.DREAM3D_base_group(fname) if base_group is None else base_group
f = h5py.File(fname, 'r') f = h5py.File(fname, 'r')
cells = f[os.path.join(b,'_SIMPL_GEOMETRY','DIMENSIONS')][()] cells = f[os.path.join(b,'_SIMPL_GEOMETRY','DIMENSIONS')][()]
size = f[os.path.join(b,'_SIMPL_GEOMETRY','SPACING')][()] * cells size = f[os.path.join(b,'_SIMPL_GEOMETRY','SPACING')] * cells
origin = f[os.path.join(b,'_SIMPL_GEOMETRY','ORIGIN')][()] origin = f[os.path.join(b,'_SIMPL_GEOMETRY','ORIGIN')][()]
ma = np.arange(cells.prod(),dtype=int) if cell_data is None else \ if feature_IDs is None:
np.reshape(f[os.path.join(b,cell_data,material)],cells.prod()) phase = f[os.path.join(b,cell_data,phases)][()].reshape(-1,1)
O = Rotation.from_Euler_angles(f[os.path.join(b,cell_data,Euler_angles)]).as_quaternion().reshape(-1,4) # noqa
unique,unique_inverse = np.unique(np.hstack([O,phase]),return_inverse=True,axis=0)
ma = np.arange(cells.prod()) if len(unique) == cells.prod() else \
np.arange(unique.size)[np.argsort(pd.unique(unique_inverse))][unique_inverse]
else:
ma = f[os.path.join(b,cell_data,feature_IDs)][()].flatten()
return Grid(ma.reshape(cells,order='F'),size,origin,util.execution_stamp('Grid','load_DREAM3D')) return Grid(ma.reshape(cells,order='F'),size,origin,util.execution_stamp('Grid','load_DREAM3D'))

View File

@ -6,6 +6,7 @@ import numpy as np
from damask import ConfigMaterial from damask import ConfigMaterial
from damask import Table from damask import Table
from damask import Rotation from damask import Rotation
from damask import Grid
@pytest.fixture @pytest.fixture
def ref_path(ref_path_base): def ref_path(ref_path_base):
@ -108,3 +109,24 @@ class TestConfigMaterial:
m = ConfigMaterial().material_add(**kw) m = ConfigMaterial().material_add(**kw)
assert len(m['material']) == N assert len(m['material']) == N
assert len(m['material'][0]['constituents']) == n assert len(m['material'][0]['constituents']) == n
@pytest.mark.parametrize('cell_ensemble_data',[None,'CellEnsembleData'])
def test_load_DREAM3D(self,ref_path,cell_ensemble_data):
grain_c = ConfigMaterial.load_DREAM3D(ref_path/'2phase_irregularGrid.dream3d','Grain Data',
cell_ensemble_data = cell_ensemble_data)
point_c = ConfigMaterial.load_DREAM3D(ref_path/'2phase_irregularGrid.dream3d',
cell_ensemble_data = cell_ensemble_data)
assert point_c.is_valid and grain_c.is_valid
assert len(point_c['material'])+1 == len(grain_c['material'])
grain_m = Grid.load_DREAM3D(ref_path/'2phase_irregularGrid.dream3d','FeatureIds').material.flatten()
point_m = Grid.load_DREAM3D(ref_path/'2phase_irregularGrid.dream3d').material.flatten()
for i in np.unique(point_m):
j = int(grain_m[(point_m==i).nonzero()[0][0]])
assert np.allclose(point_c['material'][i]['constituents'][0]['O'],
grain_c['material'][j]['constituents'][0]['O'])
assert point_c['material'][i]['constituents'][0]['phase'] == \
grain_c['material'][j]['constituents'][0]['phase']

View File

@ -420,12 +420,22 @@ class TestGrid:
t = Table(np.column_stack((coords.reshape(-1,3,order='F'),grid.material.flatten(order='F'))),{'c':3,'m':1}) t = Table(np.column_stack((coords.reshape(-1,3,order='F'),grid.material.flatten(order='F'))),{'c':3,'m':1})
assert grid_equal(grid.sort().renumber(),Grid.from_table(t,'c',['m'])) assert grid_equal(grid.sort().renumber(),Grid.from_table(t,'c',['m']))
@pytest.mark.parametrize('periodic',[True,False]) @pytest.mark.parametrize('periodic',[True,False])
@pytest.mark.parametrize('direction',['x','y','z',['x','y'],'zy','xz',['x','y','z']]) @pytest.mark.parametrize('direction',['x','y','z',['x','y'],'zy','xz',['x','y','z']])
def test_get_grain_boundaries(self,update,ref_path,periodic,direction): def test_get_grain_boundaries(self,update,ref_path,periodic,direction):
grid=Grid.load(ref_path/'get_grain_boundaries_8g12x15x20.vtr') grid = Grid.load(ref_path/'get_grain_boundaries_8g12x15x20.vtr')
current=grid.get_grain_boundaries(periodic,direction) current = grid.get_grain_boundaries(periodic,direction)
if update: if update:
current.save(ref_path/f'get_grain_boundaries_8g12x15x20_{direction}_{periodic}.vtu',parallel=False) current.save(ref_path/f'get_grain_boundaries_8g12x15x20_{direction}_{periodic}.vtu',parallel=False)
reference=VTK.load(ref_path/f'get_grain_boundaries_8g12x15x20_{"".join(direction)}_{periodic}.vtu') reference = VTK.load(ref_path/f'get_grain_boundaries_8g12x15x20_{"".join(direction)}_{periodic}.vtu')
assert current.__repr__() == reference.__repr__() assert current.__repr__() == reference.__repr__()
def test_load_DREAM3D(self,ref_path):
grain = Grid.load_DREAM3D(ref_path/'2phase_irregularGrid.dream3d','FeatureIds')
point = Grid.load_DREAM3D(ref_path/'2phase_irregularGrid.dream3d')
assert np.allclose(grain.origin,point.origin) and \
np.allclose(grain.size,point.size) and \
(grain.sort().material == point.material+1).all()