#!/usr/bin/env python

"""

----------------------------------------------------------------------------

ovf2vtk: convert OOMMF vector-field file into vtk-vector field file

SYNTAX: ovf2vtk [OPTIONS] infile outfile

OPTIONS:

        [--add xy ]
            adding a scalar component to the outfile showing the
            inplane angle of the magnetisation for the xy -plane. 
        [--add yz ]
            as --add xy but for the yz-plane.
        [--add xz ] 
            as --add xy but for the xz-plane.
        [--add Mx ] 
           adds scalar fields containing the x-component of M to the file
        [--add My ] 
           adds scalar fields containing the y-component of M to the file
        [--add Mz ] 
           adds scalar fields containing the z-component of M to the file
        [--add divrot]
           * adds a vector field containing the DIVergence of the magnetisation
           * adds a vector field containing the vorticity (=curl=ROT) of the
             magnetisation
           * adds also components of this vorticity field as scalars
        [--surface-effects]
           shows divergence and vorticity at sample surfaces (default is off)
        [--add Ms]
           adds a scalar field containing the magnitude of the magnetisation
           (this can be employed to visualise the shape of the object using
             an isosurface.)
        [--add all ]
           add all of the above
        [--binary -b]
           write binary vtk-file (this is the default, needs PyVTK>0.4)
        [--ascii -t]
           write text-vtk file
        [--help -h]
           display this text
        [--verbose -v]
           be more verbose
        [--version -V]
           Displays version number

COMMENTS:   --add can be abbreviated to -a

EXAMPLE:

 1. If the data file (for example data.ovf) is small, then the easiest
 option is to generate all additional fields available:

 ovf2vtk --add all data.ovf data.vtk

 This will create the data.vtk file, and should cover the needs of
 most users.

 2. If the data file (data.ovf) is large, then it may be advisable
 to only store the observables in data.vtk one is actually interested in.
 This line demonstrates this:

 ovf2vtk --add Mx --add My --add Mz data.ovf data.vtk

BUGS etc

    Please report bugs, problems and other feedback to Hans Fangohr
    (hans.fangohr@physics.org)
 
"""

__CVS__header__="$Header: /var/local/cvs/micromagnetics/mm0/released/ovf2vtk/bin/ovf2vtk,v 1.5 2005/07/11 11:00:34 fangohr Exp $"

__CVS__date__ = "$Date: 2005/07/11 11:00:34 $" 
__CVS__version__ = "$Revision: 1.5 $"


import sys,math,getopt,string,time,os
try:
    import pyvtk
except ImportError:
    print "This program needs pyvtk. Please download and install from  (http://cens.ioc.ee/projects/pyvtk/)"
    raise ImportError,"Couldn't import pyvtk -- cannot proceed."

try:
    import Numeric
except ImportError:
    print "This program needs Numeric. Please download and install. (http://sourceforge.net/projects/numpy)."
    raise ImportError,"Couldn't import Numeric -- cannot proceed."
    # Note: If you know what you are doing, you may be okay to replace
    # "Numeric" by numarray here, i.e. try to replace 'import Numeric'
    # with 'import numarray as Numeric'. However, this is untested.
    # (Hans Fangohr, 11 July 2005).


#import ovf2vtk
import ovf2vtk

#import tools toread omf file
from ovf2vtk.omfread import read_structured_omf_file, analyze

#import tools to compute further observables
from ovf2vtk.analysis import plane_angles, divergence_and_curl, magnitude, components

# this is the  list of keywords used by --add all
add_features = ["Ms","Mx","My","Mz","xy","yz","xz","divrot"]





#==============================================================================
#=
#= ovf2vtk
#=
#==============================================================================





def ovf2vtk_main():
    start_time = time.time()
    
    banner_doc = 70*"-"+\
    "\novf2vtk --- converting ovf files to vtk files"+"\n"+\
    "Hans Fangohr, Richard Boardman, University of Southampton\n"""+70*"-"

    #extract command line arguments
    additions,params = getopt.getopt( sys.argv[1:], 'Vvhbta:', ["verbose","help","add=","binary","text","ascii","surface-effects","version"] )

    #default value
    surfaceEffects = False

    keys = map(lambda x:x[0], additions)

    if "--surface-effects" in keys:
        surfaceEffects = True
    
    if "-v" in keys or "--verbose" in keys:
        print "running in verbose mode"
        debug = True
    else:
        debug = False

    if "-h" in keys or "--help" in keys:
        print __doc__
        sys.exit(0)

    if "-V" in keys or "--version" in keys:
        print "This is version %s." % ovf2vtk.__version__
        sys.exit(0)

    if len( params ) == 0:
        print __doc__
        print "ERROR: An input file (and an output file need to be specified)."
        sys.exit(1)
    else:
        infile = params[0]

    if len( params ) == 1:
        print __doc__
        print "ERROR: An input file AND an output file need to be specified."
        print "specify output file"
        sys.exit(1)
    else:
        outfile = params[1]

    # okay: it seems the essential parameters are given. Let's check for others:

    print banner_doc

    if debug:
        print "infile  = ", infile
        print "outfile = ",outfile
        print "OPTS    = ",additions

    #read data from infile
    vf = read_structured_omf_file( infile, debug )

    #compute magnitude for all cells 
    Ms = magnitude( vf )
    
    # Compute number of cells with non-zero Ms (rpb01r) 
    Ms_num_of_nonzeros = Numeric.sum( Numeric.not_equal( Ms, 0.0 ) ) 
    print "(%5.2f%% of %d cells filled)" % (100.0*Ms_num_of_nonzeros/len(Ms), len(Ms))

    #scale magnetisation data to unity length
    maxMs = max( Ms )
    #divide everything by maxMs (in place)
    Numeric.divide( vf, maxMs, vf )


    #
    #need x, y and z vectors for vtk format
    #

    #taking actual spacings for dx, dy and dz results generally in poor visualisation results
    #(in particular for thin films, one would like to have some magnification in z-directior).
    #We therefore have a parameter 'real_scale'. If set to "True", then the actual spacings
    #as extracted from the ovf file are used in the vtk file. If set to "False", then
    #spacings of unity are using between all cells in all three directions.
    real_scale = False

    #Warning: if you use 'real_scale=True', then one also needs to scale the data (i.e.
    #the magnetisation to be of the same order of magnitude.)

    #extract dimensions from file
    ovf_run  = analyze( infile  )
    dimensions = ( int( ovf_run["xnodes:"] ), \
                   int( ovf_run["ynodes:"] ), \
                   int( ovf_run["znodes:"] ))


    if real_scale:
        #extract x, y and z positions from ovf file.
        xbasevector = [0]* dimensions[0]
        for i in range( dimensions[0] ):
            xbasevector[i] = float( ovf_run["xbase:"] ) + i *float( ovf_run["xstepsize:"] )

        if abs( xbasevector[-1] + float( ovf_run["xbase:"] ) - float( ovf_run["xmax:"] ) ) >  float( ovf_run["xbase:"] ):
            raise "Ooops", "Something wrong with x-axis"

        ybasevector = [0]* dimensions[1]
        for i in range( dimensions[1] ):
            ybasevector[i] = float( ovf_run["ybase:"] ) + i *float( ovf_run["ystepsize:"] )

        if abs( ybasevector[-1] + float( ovf_run["ybase:"] ) - float( ovf_run["ymax:"] ) ) >  float( ovf_run["ybase:"] ):
            raise "Ooops", "Something wrong with y-axis"

        zbasevector = [0]* dimensions[2]
        for i in range( dimensions[2] ):
            zbasevector[i] = float( ovf_run["zbase:"] ) + i *float( ovf_run["zstepsize:"] )

        if abs( zbasevector[-1] + float( ovf_run["zbase:"] ) - float( ovf_run["zmax:"] ) ) >  float( ovf_run["zbase:"] ):
            raise "Ooops", "Something wrong with z-axis"
    else:
        #
        #this generally looks better:
        #
        xbasevector = Numeric.arange( dimensions[0] )
        ybasevector = Numeric.arange( dimensions[1] )
        zbasevector = Numeric.arange( dimensions[2] )


    #
    # write ascii or binary vtk-file (default is binary)
    #
    vtk_data = 'binary'
        
    if '--ascii' in keys or '-t' in keys or '--text' in keys:
        vtk_data = 'ascii'
        if debug:
            print "switching to ascii vtk-data"
    if '--binary' in keys or '-b' in keys:
        vtk_data = 'binary'
        if debug:
            print "switching to binary vtk-data"

    #
    #and now open vtk-file
    #

    vtkfilecomment =  "Output from ovf2vtk (version %s), %s, infile=%s. " % (ovf2vtk.__version__,\
                                                                   time.asctime(),\
                                                                   infile)
    vtkfilecomment += "Calling command line was '%s' executed in '%s'" % (" ".join(sys.argv),\
                                                                        os.getcwd())
        
    vtk = pyvtk.VtkData(    pyvtk.RectilinearGrid(xbasevector,ybasevector,zbasevector),
                            vtkfilecomment,
                            pyvtk.PointData(pyvtk.Vectors( vf, "Magnetisation/Ms" ) ),
                            format=vtk_data)

    #
    # now compute all the additional data such as angles, etc
    #

    # check whether we should do all
    keys = map(lambda x:x[1], additions)
    if "all" in keys:
        additions = []
        for add in add_features:
            additions.append(("--add",add))


    # when ovf2vtk was re-written using Numeric, I had to group
    # certain operations to make them fast. Now some switches are
    # unneccessary. (fangohr 25/08/2003 01:35)
    # To avoid executing the
    # same code again, we remember what we have computed already:
    
    done_angles = 0
    done_comp   = 0

    for add in additions:
        if add[0]=="-a" or add[0]=="--add":
            print "working on",add

            data=[]

            #compute observables that need more than one field value, i.e. div, rot
            if add[1][0:6] == "divrot":  #rotation = vorticity, curl
                
                (div, rot, rotx, roty, rotz, rotmag) = divergence_and_curl( vf, surfaceEffects, ovf_run )
                    
                comment = "curl of M, x-comp" 
                vtk.point_data.append( pyvtk.Scalars( rotx , comment , lookup_table='default') )
                comment = "curl of M, y-comp" 
                vtk.point_data.append( pyvtk.Scalars( roty , comment , lookup_table='default') )
                comment = "curl of M, z-comp" 
                vtk.point_data.append( pyvtk.Scalars( rotz , comment , lookup_table='default') )
                comment = "curl of M, magnitude" 
                vtk.point_data.append( pyvtk.Scalars( rotmag, comment , lookup_table='default') )
                comment = "curl of M" 
                vtk.point_data.append( pyvtk.Vectors( rot , comment ) )

                comment = "divergence of M"
                vtk.point_data.append( pyvtk.Scalars( div , comment , lookup_table='default') )

                done_div_rot = True
            elif add[1] in ["Mx","My","Mz","Ms"]:                # components
                if not done_comp:
                    done_comp = 1
                                
                    comments = "x-component of M", "y-component of M", "z-component of M"

                    for data, comment in zip( components( vf ), comments):
                        vtk.point_data.append( pyvtk.Scalars( data, comment,lookup_table='default' ) )
    
                    # magnitude of magnitisation
                    Mmag = magnitude( vf )
                    vtk.point_data.append( pyvtk.Scalars(Mmag, "Magnitude of M",lookup_table='default' ) )

            elif add[1] in ["xy","xz","yz"]:
                if not done_angles:
                    done_angles = 1

                    # in-plane angles
                    comments = "xy in-plane angle", "yz in-plane angle", "xz in-plane angle"

                    for data, comment in zip( plane_angles( vf ), comments):
                        vtk.point_data.append( pyvtk.Scalars( data, comment, lookup_table='default' )  )

            else:
                print "only xy, xz, Mx, My, Mz, divergence, Ms, or 'all' allowed after -a or --add"
                print "Current choice is",add
                print __doc__
                sys.exit(1)


    #
    #eventually, write the file
    #
    print "saving file (%s)" % (outfile)
    vtk.tofile(outfile,format=vtk_data)

    print "finished conversion (execution time %5.3s seconds)" % (time.time()-start_time)



#==============================================================================
#=
#= main
#=
#==============================================================================

if __name__ == "__main__":
    ovf2vtk_main()
    


