@@ -376,7 +376,7 @@ def read_grain_file(filename):
376376STRINGATTRS = ["intensity_info" , "name" ]
377377NUMATTRS = ["npks" , "nuniq" ]
378378ARRATTRS = ["translation" ]
379-
379+ MATATTRS = [ "UB" , "ubi" , "B" , "U" , "mt" , "rmt" ]
380380
381381def write_grain_file_h5 (filename , list_of_grains , group_name = 'grains' ):
382382 """Write list of grains to H5py file."""
@@ -410,3 +410,125 @@ def read_grain_file_h5(filename, group_name='grains'):
410410 grains .append (g )
411411
412412 return grains
413+
414+
415+ def export_to_vtp_paraview (grains , output_path , colour = 'npks' ):
416+ """
417+ Export list of grains to vtp file for Paraview visualisation, with helpful arrays included.
418+ Thanks to Marilyn Sarkis for the idea.
419+ """
420+ import vtk
421+ from vtk .util import numpy_support as ns
422+ from scipy .spatial .transform import Rotation as R
423+
424+ def add_scalar_attribute (pd , attr , np_array ):
425+ """Add scalar attribute array to polydata"""
426+ attr_array = ns .numpy_to_vtk (np_array , deep = True )
427+ attr_array .SetName (attr )
428+ pd .GetPointData ().AddArray (attr_array )
429+
430+ def add_vector_attribute (pd , attr , np_array ):
431+ """Add vector attribute array to polydata"""
432+ attr_array = ns .numpy_to_vtk (np_array , deep = True )
433+ attr_array .SetNumberOfComponents (np_array .shape [1 ]) # required for vectors
434+ attr_array .SetName (attr )
435+ pd .GetPointData ().AddArray (attr_array )
436+
437+ # ensure we have ipf colours
438+ rgbattr = 'rgb_z'
439+ try :
440+ col = [getattr (grain , rgbattr ) for grain in grains ] # IPF colour
441+ except AttributeError :
442+ # couldn't get the IPF attributes
443+ # try to compute it first
444+ # will still fail if we don't have reference unitcells
445+ from ImageD11 .nbGui .nb_utils import get_rgbs_for_grains
446+ get_rgbs_for_grains (grains )
447+ col = [getattr (grain , rgbattr ) for grain in grains ] # IPF colour
448+
449+ translations = np .array ([grain .translation for grain in grains ])
450+
451+ points = vtk .vtkPoints ()
452+ verts = vtk .vtkCellArray ()
453+
454+ for i , t in enumerate (translations ):
455+ pid = points .InsertNextPoint (t [0 ], t [1 ], t [2 ])
456+ verts .InsertNextCell (1 )
457+ verts .InsertCellPoint (pid )
458+
459+ polydata = vtk .vtkPolyData ()
460+ polydata .SetPoints (points )
461+ polydata .SetVerts (verts )
462+
463+ # add scalar attributes to polydata
464+ for numattr in NUMATTRS :
465+ np_array = np .array ([float (getattr (grain , numattr )) for grain in grains ])
466+ add_scalar_attribute (polydata , numattr , np_array )
467+
468+ # compute volumes
469+ prop_volumes = np .array ([float (grain .intensity_info .split ("mean = " )[1 ].split (" , " )[0 ].replace ("'" , "" )) for grain in grains ])
470+ prop_radii = np .cbrt (3 * prop_volumes / (4 * np .pi ))
471+
472+ add_scalar_attribute (polydata , 'prop_volume' , prop_volumes )
473+ add_scalar_attribute (polydata , 'prop_radius' , prop_radii )
474+
475+ # add vector attributes (e.g. colours)
476+ for vecattr in ["rgb_x" , "rgb_y" , "rgb_z" , "Rod" , "unitcell" ]:
477+ np_array = np .array ([getattr (grain , vecattr ) for grain in grains ])
478+ add_vector_attribute (polydata , vecattr , np_array )
479+
480+ # compute quaternions for glyph orientation in Paraview
481+
482+ quats = R .from_matrix ([g .U for g in grains ]).as_quat (scalar_first = True ) # WXYZ order to match Paraview
483+
484+ add_vector_attribute (polydata , 'quat' , quats )
485+
486+ # it's also useful to have lab-frame vector fields for (x_c, y_c, z_c) and (a, b, c) and (astar, bstar, cstar) for viz
487+ # x_c, y_c, z_c are columns of U
488+ add_vector_attribute (polydata , "x_c" , np .array ([g .U [:, 0 ] for g in grains ]))
489+ add_vector_attribute (polydata , "y_c" , np .array ([g .U [:, 1 ] for g in grains ]))
490+ add_vector_attribute (polydata , "z_c" , np .array ([g .U [:, 2 ] for g in grains ]))
491+
492+ # a, b, c are rows of UBI (because we invert)
493+ add_vector_attribute (polydata , "a" , np .array ([g .ubi [0 , :] for g in grains ]))
494+ add_vector_attribute (polydata , "b" , np .array ([g .ubi [1 , :] for g in grains ]))
495+ add_vector_attribute (polydata , "c" , np .array ([g .ubi [2 , :] for g in grains ]))
496+
497+ # a*, b*, c* are columns of UB
498+ add_vector_attribute (polydata , "astar" , np .array ([g .UB [:, 0 ] for g in grains ]))
499+ add_vector_attribute (polydata , "bstar" , np .array ([g .UB [:, 1 ] for g in grains ]))
500+ add_vector_attribute (polydata , "cstar" , np .array ([g .UB [:, 2 ] for g in grains ]))
501+
502+ # add matrix attributes
503+ for matattr in MATATTRS :
504+ tensor_components = [
505+ ('11' , 0 , 0 ), ('12' , 0 , 1 ), ('13' , 0 , 2 ),
506+ ('21' , 1 , 0 ), ('22' , 1 , 1 ), ('23' , 1 , 2 ),
507+ ('31' , 2 , 0 ), ('32' , 2 , 1 ), ('33' , 2 , 2 )
508+ ]
509+ for comp_name , i , j in tensor_components :
510+ attr_name = "{}_{}" .format (matattr , comp_name )
511+ np_array = np .array ([getattr (grain , matattr )[i , j ] for grain in grains ])
512+ add_scalar_attribute (polydata , attr_name , np_array )
513+
514+ eps_s = np .array ([grain .eps_sample_matrix (dzero_cell = grain .ref_unitcell .lattice_parameters ) for grain in grains ])
515+ eps_c = np .array ([grain .eps_grain_matrix (dzero_cell = grain .ref_unitcell .lattice_parameters ) for grain in grains ])
516+
517+ # it's a strain tensor, so we can name them xx, yy, zz etc
518+ tensor_components = [
519+ ('xx' , 0 , 0 ), ('xy' , 0 , 1 ), ('xz' , 0 , 2 ),
520+ ('yx' , 1 , 0 ), ('yy' , 1 , 1 ), ('yz' , 1 , 2 ),
521+ ('zx' , 2 , 0 ), ('zy' , 2 , 1 ), ('zz' , 2 , 2 )
522+ ]
523+ for comp_name , i , j in tensor_components :
524+ add_scalar_attribute (polydata , "eps_s_{}" .format (comp_name ), eps_s [:,i ,j ])
525+ add_scalar_attribute (polydata , "eps_c_{}" .format (comp_name ), eps_c [:,i ,j ])
526+
527+ # set the active scalars
528+ polydata .GetPointData ().SetActiveScalars (colour )
529+
530+ # export to file
531+ writer = vtk .vtkXMLPolyDataWriter ()
532+ writer .SetFileName (output_path )
533+ writer .SetInputData (polydata )
534+ writer .Write ()
0 commit comments