From 394fda5f377d3a2a69db0fe071577b7e89ecc8f8 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 20 Mar 2021 12:51:41 +0100 Subject: [PATCH] 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 --- python/damask/_configmaterial.py | 84 +++++++++-------------------- python/damask/_grid.py | 21 +++++--- python/tests/test_ConfigMaterial.py | 22 ++++++++ python/tests/test_Grid.py | 16 ++++-- 4 files changed, 75 insertions(+), 68 deletions(-) diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index 0f6589725..4c126f281 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -1,4 +1,4 @@ -from os import path +import os.path import numpy as np import h5py @@ -88,7 +88,7 @@ class ConfigMaterial(Config): phase: {} """ - kwargs_ = {k:table.get(v) for k,v in kwargs.items()} + kwargs_ = {k:table.get(v) for k,v in kwargs.items()} _,idx = np.unique(np.hstack(list(kwargs_.values())),return_index=True,axis=0) idx = np.sort(idx) @@ -98,7 +98,10 @@ class ConfigMaterial(Config): @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. @@ -107,70 +110,33 @@ class ConfigMaterial(Config): Parameters ---------- fname : str - path to the DREAM3D file. + Filename of the DREAM.3D (HDF5) file. base_group : str - Name of the group (folder) below 'DataContainers', - for example 'SyntheticVolumeDataContainer'. - data_group : str - 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']) + Path to the group (folder) that contains the geometry (_SIMPL_GEOMETRY), + and, optionally, the cell data. Defaults to None, in which case + it is set as the path that contains _SIMPL_GEOMETRY/SPACING. """ 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 hdf[orientation_path].attrs['TupleDimensions'].shape == (3,): - grain_orientations = np.array(hdf[orientation_path]).reshape(-1,3,order='F') + if grain_data is None: + phase = f[os.path.join(b,cell_data,phases)][()].flatten() + 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: - 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) - if hdf[phase_path].attrs['TupleDimensions'].shape == (3,): - grain_phase = np.array(hdf[phase_path]).reshape(-1,order='F') - 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 + material = {k:np.atleast_1d(v[idx].squeeze()) for k,v in zip(['O','phase'],[O,phase])} + return ConfigMaterial({'phase':{k if isinstance(k,int) else str(k):'tbd' for k in np.unique(phase)}, + 'homogenization':{'direct':{'N_constituents':1}}}).material_add(**material) @property diff --git a/python/damask/_grid.py b/python/damask/_grid.py index 2d161495b..da6938c7f 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -256,14 +256,17 @@ class Grid: @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. Parameters ---------- fname : str - Filename of the DREAM.3D file + Filename of the DREAM.3D (HDF5) file. cell_data : str, optional Name of the group (folder) containing the pointwise material data, for example 'CellData'. Defaults to None, in which case points are consecutively numbered. @@ -274,16 +277,22 @@ class Grid: Path to the group (folder) that contains the geometry (_SIMPL_GEOMETRY), and, optionally, the cell data. Defaults to None, in which case it is set as the path that contains _SIMPL_GEOMETRY/SPACING. - + """ b = util.DREAM3D_base_group(fname) if base_group is None else base_group f = h5py.File(fname, 'r') 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')][()] - ma = np.arange(cells.prod(),dtype=int) if cell_data is None else \ - np.reshape(f[os.path.join(b,cell_data,material)],cells.prod()) + if feature_IDs is None: + 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')) diff --git a/python/tests/test_ConfigMaterial.py b/python/tests/test_ConfigMaterial.py index 5eb9a6c85..4e837999d 100644 --- a/python/tests/test_ConfigMaterial.py +++ b/python/tests/test_ConfigMaterial.py @@ -6,6 +6,7 @@ import numpy as np from damask import ConfigMaterial from damask import Table from damask import Rotation +from damask import Grid @pytest.fixture def ref_path(ref_path_base): @@ -108,3 +109,24 @@ class TestConfigMaterial: m = ConfigMaterial().material_add(**kw) assert len(m['material']) == 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'] diff --git a/python/tests/test_Grid.py b/python/tests/test_Grid.py index a239165db..e3ca37982 100644 --- a/python/tests/test_Grid.py +++ b/python/tests/test_Grid.py @@ -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}) assert grid_equal(grid.sort().renumber(),Grid.from_table(t,'c',['m'])) + @pytest.mark.parametrize('periodic',[True,False]) @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): - grid=Grid.load(ref_path/'get_grain_boundaries_8g12x15x20.vtr') - current=grid.get_grain_boundaries(periodic,direction) + grid = Grid.load(ref_path/'get_grain_boundaries_8g12x15x20.vtr') + current = grid.get_grain_boundaries(periodic,direction) if update: 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__() + + + 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()