2015-07-24 19:00:33 +05:30
#!/usr/bin/env python
2015-08-24 04:49:40 +05:30
import os , sys , string , itertools , re , time , copy , operator
2015-07-24 19:00:33 +05:30
import numpy as np
import damask
from scipy import spatial
from collections import defaultdict
from optparse import OptionParser , OptionGroup , Option , SUPPRESS_HELP
scriptID = string . replace ( ' $Id: addGrainID.py 2549 2013-07-10 09:13:21Z MPIE \ p.eisenlohr $ ' , ' \n ' , ' \\ n ' )
scriptName = os . path . splitext ( scriptID . split ( ) [ 1 ] ) [ 0 ]
2015-08-08 00:33:26 +05:30
parser = OptionParser ( option_class = damask . extendableOption , usage = ' % prog options [file[s]] ' , description = """
2015-07-24 19:00:33 +05:30
Add grain index based on similiarity of crystal lattice orientation .
""" + string.replace(scriptID, ' \n ' , ' \\ n ' )
)
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -r ' , ' --radius ' ,
dest = ' radius ' ,
type = ' float ' , metavar = ' float ' ,
2015-07-24 19:00:33 +05:30
help = ' search radius ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -d ' , ' --disorientation ' ,
dest = ' disorientation ' ,
type = ' float ' , metavar = ' float ' ,
2015-07-24 19:00:33 +05:30
help = ' disorientation threshold per grain [ %d efault] (degrees) ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -s ' , ' --symmetry ' ,
dest = ' symmetry ' ,
type = ' string ' , metavar = ' string ' ,
2015-07-24 19:00:33 +05:30
help = ' crystal symmetry [ %d efault] ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -e ' , ' --eulers ' ,
dest = ' eulers ' ,
type = ' string ' , metavar = ' string ' ,
2015-07-24 19:00:33 +05:30
help = ' Euler angles ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' --degrees ' ,
dest = ' degrees ' ,
action = ' store_true ' ,
2015-07-24 19:00:33 +05:30
help = ' Euler angles are given in degrees [ %d efault] ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -m ' , ' --matrix ' ,
dest = ' matrix ' ,
type = ' string ' , metavar = ' string ' ,
2015-07-24 19:00:33 +05:30
help = ' orientation matrix ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -a ' ,
dest = ' a ' ,
type = ' string ' , metavar = ' string ' ,
2015-07-24 19:00:33 +05:30
help = ' crystal frame a vector ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -b ' ,
dest = ' b ' ,
type = ' string ' , metavar = ' string ' ,
2015-07-24 19:00:33 +05:30
help = ' crystal frame b vector ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -c ' ,
dest = ' c ' ,
type = ' string ' , metavar = ' string ' ,
2015-07-24 19:00:33 +05:30
help = ' crystal frame c vector ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -q ' , ' --quaternion ' ,
dest = ' quaternion ' ,
type = ' string ' , metavar = ' string ' ,
2015-07-24 19:00:33 +05:30
help = ' quaternion ' )
2015-08-08 00:33:26 +05:30
parser . add_option ( ' -p ' , ' --position ' ,
dest = ' coords ' ,
type = ' string ' , metavar = ' string ' ,
2015-07-24 19:00:33 +05:30
help = ' spatial position of voxel [ %d efault] ' )
2015-08-08 00:33:26 +05:30
parser . set_defaults ( symmetry = ' cubic ' ,
coords = ' pos ' ,
degrees = False ,
)
2015-07-24 19:00:33 +05:30
( options , filenames ) = parser . parse_args ( )
if options . radius == None :
parser . error ( ' no radius specified. ' )
2015-08-08 00:33:26 +05:30
input = [ options . eulers != None ,
options . a != None and \
options . b != None and \
options . c != None ,
options . matrix != None ,
options . quaternion != None ,
]
if np . sum ( input ) != 1 : parser . error ( ' needs exactly one input format. ' )
( label , dim , inputtype ) = [ ( options . eulers , 3 , ' eulers ' ) ,
( [ options . a , options . b , options . c ] , [ 3 , 3 , 3 ] , ' frame ' ) ,
( options . matrix , 9 , ' matrix ' ) ,
( options . quaternion , 4 , ' quaternion ' ) ,
] [ np . where ( input ) [ 0 ] [ 0 ] ] # select input label that was requested
toRadians = math . pi / 180.0 if options . degrees else 1.0 # rescale degrees to radians
# --- loop over input files -------------------------------------------------------------------------
2015-08-21 01:12:05 +05:30
if filenames == [ ] : filenames = [ None ]
2015-08-08 00:33:26 +05:30
for name in filenames :
2015-08-21 01:12:05 +05:30
try :
table = damask . ASCIItable ( name = name ,
buffered = False )
except : continue
2015-09-11 18:25:43 +05:30
table . report_name ( scriptName , name )
2015-08-08 00:33:26 +05:30
# ------------------------------------------ read header -------------------------------------------
table . head_read ( )
# ------------------------------------------ sanity checks -----------------------------------------
2015-07-24 19:00:33 +05:30
2015-08-08 00:33:26 +05:30
errors = [ ]
remarks = [ ]
if table . label_dimension ( options . coords ) != 3 : errors . append ( ' coordinates {} are not a vector. ' . format ( options . coords ) )
if not np . all ( table . label_dimension ( label ) == dim ) : errors . append ( ' input {} has wrong dimension {} . ' . format ( label , dim ) )
else : column = table . label_index ( label )
if remarks != [ ] : table . croak ( remarks )
if errors != [ ] :
table . croak ( errors )
table . close ( dismiss = True )
continue
2015-07-24 19:00:33 +05:30
# ------------------------------------------ assemble header ---------------------------------------
2015-08-08 00:33:26 +05:30
table . info_append ( scriptID + ' \t ' + ' ' . join ( sys . argv [ 1 : ] ) )
table . labels_append ( ' grainID_ {} @ {} ' . format ( ' , ' . join ( labels ) , options . disorientation / toRadians ) ) # report orientation source and disorientation in degrees
2015-07-24 19:00:33 +05:30
table . head_write ( )
2015-08-08 00:33:26 +05:30
# ------------------------------------------ process data ------------------------------------------
2015-07-24 19:00:33 +05:30
2015-08-08 00:33:26 +05:30
# ------------------------------------------ build KD tree -----------------------------------------
2015-07-24 19:00:33 +05:30
# --- start background messaging
2015-08-24 04:49:40 +05:30
bg = damask . util . backgroundMessage ( )
2015-07-24 19:00:33 +05:30
bg . start ( )
bg . set_message ( ' reading positions... ' )
2015-08-08 00:33:26 +05:30
table . data_readArray ( options . coords ) # read position vectors
2015-07-24 19:00:33 +05:30
grainID = - np . ones ( len ( table . data ) , dtype = int )
start = tick = time . clock ( )
bg . set_message ( ' building KD tree... ' )
kdtree = spatial . KDTree ( copy . deepcopy ( table . data ) )
2015-08-08 00:33:26 +05:30
# ------------------------------------------ assign grain IDs --------------------------------------
2015-07-24 19:00:33 +05:30
2015-08-08 00:33:26 +05:30
tick = time . clock ( )
2015-07-24 19:00:33 +05:30
2015-08-08 00:33:26 +05:30
orientations = [ ] # quaternions found for grain
memberCounts = [ ] # number of voxels in grain
p = 0 # point counter
g = 0 # grain counter
2015-07-24 19:00:33 +05:30
matchedID = - 1
2015-08-08 00:33:26 +05:30
lastDistance = np . dot ( kdtree . data [ - 1 ] - kdtree . data [ 0 ] , kdtree . data [ - 1 ] - kdtree . data [ 0 ] ) # (arbitrarily) use diagonal of cloud
2015-07-24 19:00:33 +05:30
2015-08-08 00:33:26 +05:30
table . data_rewind ( )
while table . data_read ( ) : # read next data line of ASCII table
2015-07-24 19:00:33 +05:30
if p > 0 and p % 1000 == 0 :
time_delta = ( time . clock ( ) - tick ) * ( len ( grainID ) - p ) / p
bg . set_message ( ' ( %02i : %02i : %02i ) processing point %i of %i (grain count %i )... ' % ( time_delta / / 3600 , time_delta % 3600 / / 60 , time_delta % 60 , p , len ( grainID ) , len ( orientations ) ) )
2015-08-08 00:33:26 +05:30
if inputtype == ' eulers ' :
o = damask . Orientation ( Eulers = np . array ( map ( float , table . data [ column : column + 3 ] ) ) * toRadians ,
symmetry = options . symmetry ) . reduced ( )
elif inputtype == ' matrix ' :
o = damask . Orientation ( matrix = np . array ( map ( float , table . data [ column : column + 9 ] ) ) . reshape ( 3 , 3 ) . transpose ( ) ,
symmetry = options . symmetry ) . reduced ( )
elif inputtype == ' frame ' :
o = damask . Orientation ( matrix = np . array ( map ( float , table . data [ column [ 0 ] : column [ 0 ] + 3 ] + \
table . data [ column [ 1 ] : column [ 1 ] + 3 ] + \
table . data [ column [ 2 ] : column [ 2 ] + 3 ] ) ) . reshape ( 3 , 3 ) ,
symmetry = options . symmetry ) . reduced ( )
elif inputtype == ' quaternion ' :
o = damask . Orientation ( quaternion = np . array ( map ( float , table . data [ column : column + 4 ] ) ) ,
symmetry = options . symmetry ) . reduced ( )
2015-07-24 19:00:33 +05:30
matched = False
# check against last matched needs to be really picky. best would be to exclude jumps across the poke (checking distance between last and me?)
# when walking through neighborhood first check whether grainID of that point has already been tested, if yes, skip!
2015-08-08 00:33:26 +05:30
if matchedID != - 1 : # has matched before?
2015-07-24 19:00:33 +05:30
matched = ( o . quaternion . conjugated ( ) * orientations [ matchedID ] . quaternion ) . w > cos_disorientation
if not matched :
alreadyChecked = { }
2015-08-24 19:50:09 +05:30
bestDisorientation = damask . Quaternion ( [ 0 , 0 , 0 , 1 ] ) # initialize to 180 deg rotation as worst case
2015-08-08 00:33:26 +05:30
for i in kdtree . query_ball_point ( kdtree . data [ p ] , options . radius ) : # check all neighboring points
2015-07-24 19:00:33 +05:30
gID = grainID [ i ]
2015-08-08 00:33:26 +05:30
if gID != - 1 and gID not in alreadyChecked : # an already indexed point belonging to a grain not yet tested?
alreadyChecked [ gID ] = True # remember not to check again
2015-08-24 19:50:09 +05:30
disorientation = o . disorientation ( orientations [ gID ] ) [ 0 ] # compare against that grain's orientation
if disorientation . w > cos_disorientation and \
disorientation . w > = bestDisorientation . w : # within disorientation threshold and better than current best?
2015-07-24 19:00:33 +05:30
matched = True
2015-08-08 00:33:26 +05:30
matchedID = gID # remember that grain
2015-07-24 19:00:33 +05:30
bestDisorientation = disorientation
2015-08-08 00:33:26 +05:30
if not matched : # no match -> new grain found
memberCounts + = [ 1 ] # start new membership counter
orientations + = [ o ] # initialize with current orientation
2015-07-24 19:00:33 +05:30
matchedID = g
2015-08-08 00:33:26 +05:30
g + = 1 # increment grain counter
2015-07-24 19:00:33 +05:30
2015-08-08 00:33:26 +05:30
else : # did match existing grain
2015-07-24 19:00:33 +05:30
memberCounts [ matchedID ] + = 1
2015-08-08 00:33:26 +05:30
grainID [ p ] = matchedID # remember grain index assigned to point
p + = 1 # increment point
bg . set_message ( ' identifying similar orientations among {} grains... ' . format ( len ( orientations ) ) )
2015-07-24 19:00:33 +05:30
memberCounts = np . array ( memberCounts )
similarOrientations = [ [ ] for i in xrange ( len ( orientations ) ) ]
2015-08-24 19:50:09 +05:30
for i , orientation in enumerate ( orientations [ : - 1 ] ) : # compare each identified orientation...
2015-08-08 00:33:26 +05:30
for j in xrange ( i + 1 , len ( orientations ) ) : # ...against all others that were defined afterwards
2015-08-24 19:50:09 +05:30
if orientation . disorientation ( orientations [ j ] ) [ 0 ] . w > cos_disorientation : # similar orientations in both grainIDs?
2015-08-08 00:33:26 +05:30
similarOrientations [ i ] . append ( j ) # remember in upper triangle...
similarOrientations [ j ] . append ( i ) # ...and lower triangle of matrix
2015-07-24 19:00:33 +05:30
if similarOrientations [ i ] != [ ] :
2015-08-24 19:50:09 +05:30
bg . set_message ( ' grainID {} is as: {} ' . format ( i , ' ' . join ( map ( str , similarOrientations [ i ] ) ) ) )
2015-07-24 19:00:33 +05:30
stillShifting = True
while stillShifting :
stillShifting = False
tick = time . clock ( )
2015-08-08 00:33:26 +05:30
for p , gID in enumerate ( grainID ) : # walk through all points
2015-07-24 19:00:33 +05:30
if p > 0 and p % 1000 == 0 :
time_delta = ( time . clock ( ) - tick ) * ( len ( grainID ) - p ) / p
bg . set_message ( ' ( %02i : %02i : %02i ) shifting ID of point %i out of %i (grain count %i )... ' % ( time_delta / / 3600 , time_delta % 3600 / / 60 , time_delta % 60 , p , len ( grainID ) , len ( orientations ) ) )
2015-08-08 00:33:26 +05:30
if similarOrientations [ gID ] != [ ] : # orientation of my grainID is similar to someone else?
similarNeighbors = defaultdict ( int ) # dict holding frequency of neighboring grainIDs that share my orientation (freq info not used...)
for i in kdtree . query_ball_point ( kdtree . data [ p ] , options . radius ) : # check all neighboring points
if grainID [ i ] in similarOrientations [ gID ] : # neighboring point shares my orientation?
similarNeighbors [ grainID [ i ] ] + = 1 # remember its grainID
if similarNeighbors != { } : # found similar orientation(s) in neighborhood
candidates = np . array ( [ gID ] + similarNeighbors . keys ( ) ) # possible replacement grainIDs for me
grainID [ p ] = candidates [ np . argsort ( memberCounts [ candidates ] ) [ - 1 ] ] # adopt ID that is most frequent in overall dataset
memberCounts [ gID ] - = 1 # my former ID loses one fellow
memberCounts [ grainID [ p ] ] + = 1 # my new ID gains one fellow
bg . set_message ( ' {} : {} --> {} ' . format ( p , gID , grainID [ p ] ) ) # report switch of grainID
2015-07-24 19:00:33 +05:30
stillShifting = True
table . data_rewind ( )
2015-08-24 19:50:09 +05:30
outputAlive = True
2015-07-24 19:00:33 +05:30
p = 0
2015-08-24 19:50:09 +05:30
while outputAlive and table . data_read ( ) : # read next data line of ASCII table
2015-08-08 00:33:26 +05:30
table . data_append ( 1 + grainID [ p ] ) # add grain ID
2015-08-24 19:50:09 +05:30
outputAlive = table . data_write ( ) # output processed line
2015-07-24 19:00:33 +05:30
p + = 1
2015-08-08 00:33:26 +05:30
bg . set_message ( ' done after {} seconds ' . format ( time . clock ( ) - start ) )
2015-07-24 19:00:33 +05:30
2015-08-08 00:33:26 +05:30
# ------------------------------------------ output finalization -----------------------------------
2015-07-24 19:00:33 +05:30
2015-08-08 00:33:26 +05:30
table . close ( ) # close ASCII tables