>>>>> "Massimo" == DiPierro, Massimo <[EMAIL PROTECTED]> writes:

    Massimo> www.fermiqcd.net <http://www.fermiqcd.net/> and I am
    Massimo> starting a new project for visualization in lattice QCD.

I've attached some code which you may find useful as a starter.  Note
that it will NOT be good for you right away because it is quite old,
written for Mayavi1, plain VTK and Numeric.

But still, some of what's there may provide a first cut.  There are
classes in there to load Topological Densities in the format produced
by the version 5 MILC code in pure gauge configurations (from the
binary lattice files).  My use for it was to slice the field
configurations (I also did some fermion density plots using the same
code) in any direction and generate HTML tables of isosurfaces for
easy inspection of a large number of lattices.  I found it easier to
look at the data this way by dumping 'contact sheets' of the field
config than to manually load hundreds of configs.

If this of any use to you, do with it as you wish; I don't use this
code at all anymore.

Cheers,

f
"""Miscellaneous utilities for VTK programming."""

__author__ = 'Fernando Perez'
__email__ = '[EMAIL PROTECTED]'

#--------------------------------------------------------------------------
# Needed modules
import types,copy
from mayavi import ivtk
from vtkpython import *

#--------------------------------------------------------------------------
# Functions

def __make_globals():
    global opacity_fns,color_fns

    class table:pass  # dummy class

    # Opacity functions
    op = table()
    op.flat = [(0,1),(1,1)] # minimal valid list without symmetrization
    # various v-shaped functions which hide mid-range values
    op.simple_v = {'points':[(0,1),(0.5,0)],'symmetric':1}
    op.v1 = {'points':[(0,1),(.1,.9),(.3,.2),(.5,0)],'symmetric':1}
    op.v2 = {'points':[(0,1),(.05,.8),(.15,.5),(.3,0.05),(.4,0.01),(.5,0)],
             'symmetric':1}
    op.v3 = {'points':[(0,1),(.05,.8),(.15,.5),(.3,0.01),(.4,0.0),(.5,0)],
             'symmetric':1}
    op.v4 = {'points':[(0.0, 0.94948854441504127),
                       (0.035714285714285712, 0.80424645547447116),
                       (0.071428571428571425, 0.65109382296701002),
                       (0.10714285714285714, 0.49696568131514102),
                       (0.14285714285714285, 0.35009836810263129),
                       (0.17857142857142855, 0.2193496591388204),
                       (0.21428571428571427, 0.11333665732781846),
                       (0.25, 0.039504140754667641),
                       (0.2857142857142857, 0.0032619764428993394),
                       (0.3214285714285714, 0.0),
                       (0.5, 0.0)],
             'symmetric':1}
    op.v5 = {'points':[(0.0, 0.99762935250668083),
                       (0.035714285714285712, 0.9184236784755615),
                       (0.071428571428571425, 0.82533229455592594),
                       (0.10714285714285714, 0.71980451982071758),
                       (0.14285714285714285, 0.60472644671805775),
                       (0.17857142857142855, 0.48445139475713539),
                       (0.21428571428571427, 0.36463421932744544),
                       (0.25, 0.25185472655077878),
                       (0.2857142857142857, 0.15305251748275239),
                       (0.3214285714285714, 0.074834654304759413),
                       (0.3571428571428571, 0.02274961903772077),
                       (0.39285714285714285, 0.00063759243707800506),
                       (0.42857142857142855, 0.0),
                       (0.5, 0.0)],
             'symmetric':1}
    
    op.v_gauss = {'points':[(0.0, 0.99326205300091452),
                            (0.035714285714285712, 0.98658288958329921),
                            (0.071428571428571425, 0.97461176465766319),
                            (0.10714285714285714, 0.95434922494481678),
                            (0.14285714285714285, 0.92199796853146232),
                            (0.17857142857142855, 0.87334982624593316),
                            (0.21428571428571427, 0.80458955009027378),
                            (0.25, 0.71349520313980985),
                            (0.2857142857142857, 0.60082978415651644),
                            (0.3214285714285714, 0.47152252861281918),
                            (0.3571428571428571, 0.33512968029560419),
                            (0.39285714285714285, 0.2051420318745103),
                            (0.42857142857142855, 0.097007305927970022),
                            (0.46428571428571425, 0.025187568150640605),
                            (0.5, 0.0)],
                  'symmetric':1}
                  
    [ 0.99326205,  0.98173503,  0.95568107,  0.90374171,  0.81286037,  0.67433423,
             0.49270894,  0.29267172,  0.11719747,  0.01375494,  0.01375494,
             0.11719747,  0.29267172,  0.49270894,  0.67433423,  0.81286037,
             0.90374171,  0.95568107,  0.98173503,  0.99326205]
    op.sq = {'points':[(0,1),(.25,1),(.26,0),(.5,0)],'symmetric':1}

    # Color functions
    cf = table()
    cf.red_turq = {'points':[(0,1,0,0),(.5,1,1,0),(1,0,1,1)],'mode':'RGB'}
    cf.red_blue = {'points':[(0,0,1,1),(1,0.667,1,1)],'mode':'HSV'}
    cf.blue_red = {'points':[(0,0.667,1,1),(1,0,1,1)],'mode':'HSV'}

    # set globals
    opacity_fns = op
    color_fns = cf

__make_globals()


def symmetrize(lst):
    """Symmetrize a list of (x,y) pairs about the endpoint.
    Returns a list with 2n-1 elements (n=len(lst)) which is now
    symmetric about its midpoint.

    The original list is unchanged.

    Note. The algorithm is simple and inefficient. This is meant to be
    used for simple cases such as transfer functions in volume rendering,
    so zero effort went into optimizing it. Don't use this to manipulate
    huge lists."""

    mirror = copy.deepcopy(lst)
    mirror.reverse()
    sym = copy.deepcopy(lst)
    x_end = 2*lst[-1][0]
    midpt = len(sym)
    sym.extend(mirror[1:])
    for n in range(midpt,len(sym)):
        sym[n] = (x_end - sym[n][0],sym[n][1])
    return sym


def build_PiecewiseFunction(pwf,nmin,nmax,pts):
    """Build a vtkPiecewiseFunction given a list of points.

    build_PiecewiseFunction(pwf,nmin,nmax,pts):
    
    -pwf: vtkPiecewiseFunction object, modified in-place.

    -nmin,nmax: range of input values for the function (0,255 typically)

    -pts: a list or tuple of (x,y) pairs mapped to the [0,1] interval.  The
    first pair must be (0,y0) and the last must be (1,y_fin) to ensure the
    function is defined over the complete (nmin,nmax) range unless the data is
    to be symmetrized.

    In order to symmetrize a list, pts must instead be a dict with the form:
    pts = {'points':[(0,y0),...,(0.5,y_mid)],'symmetric':1}

    -symmetric: if 1, the last pair must be (0.5,y_fin) and the list willl
    be symmetrized about its midpoint."""

    if type(pts) == types.DictType:
        if pts['symmetric']:
            pts = symmetrize(pts['points'])
        else:
            raise ValueError,'dicts can only be used to symmetrize lists'

    if pts[0][0] != 0 or pts[-1][0] != 1:
        raise ValueError,'first,last pair must be (0,y0) and (1,y_fin)'
    for pt in pts:
        pwf.AddPoint(nmin+int(pt[0]*nmax),pt[1])


def build_ColorTransferFunction(ctf,nmin,nmax,pts):
    """Build a vtkColorTransferFunction given a list of points.

    Similar to build_PiecewiseFunction(), except it doesn't have the
    symmetrization options.

    pts is a dict of the form:
    {'points':[(0,v1_1,v1_2,v1_3),...,(1,vN_1,vN_2,vN_3)],
     'mode':'RGB'}

    Use mode 'RGB' (default) or 'HSV' to specify how the above vi_j are to be
    interpreted."""
    
    
    if pts['points'][0][0] != 0 or pts['points'][-1][0] != 1:
        raise ValueError,'first,last pair MUST be (0,y0) and (1,y_fin)'
    if pts['mode'] == 'RGB':
        add_segment = vtkColorTransferFunction.AddRGBSegment
    elif pts['mode'] == 'HSV':
        add_segment = vtkColorTransferFunction.AddHSVSegment
    else:
        raise ValueError,"mode can only be 'RGB' or 'HSV'"

    # We must do it with segments to be able to build tables which go
    # 'backwards' in colorspace (doesn't work with AddPoint)
    pt = pts['points']
    for n in range(len(pt)-1):
        ptn = pt[n]
        add_segment(ctf,nmin+int(ptn[0]*nmax),ptn[1],ptn[2],ptn[3],
                    nmin+int(pt[n+1][0]*nmax),*pt[n+1][1:4])


def volume_render(vtk_file,
                  mapper='texture',
                  dsample = 1,
                  opacity_function = opacity_fns.v3,
                  color_function = color_fns.blue_red,
                  background_color = (0.93,0.93,0.84),
                  outline_color = (0.1,0.1,0.1) ):
    """Open a VTK volume renderer for the given file.

    Details for some parameters (the others are self-explanatory):

    - mapper: 'ray' or 'texture' (default). Ray tracing is MUCH slower.

    - dsample: distance between samples for ray tracing
    interpolation. Extremely slow if < 1, but gives very nice images.

    """
    # Build the vtk pipeline
    # Data reader
    reader = vtkStructuredPointsReader()
    reader.SetFileName(vtk_file)

    # Texture mappers are fairly fast
    textureMapper = vtkVolumeTextureMapper2D()
    textureMapper.SetInput(reader.GetOutput())

    # We can also use a much slower ray tracing mapper
    rayMapper = vtkVolumeRayCastMapper()
    rayMapper.SetInput(reader.GetOutput())
    compositeFunction = vtkVolumeRayCastCompositeFunction()
    rayMapper.SetVolumeRayCastFunction(compositeFunction)
    rayMapper.SetSampleDistance(dsample)  # VERY SLOW if dsample < 1

    # Transfer functions for opacity and color
    nmin = 0; nmax=255 # volumetric data is in bytes

    opacity = vtkPiecewiseFunction()
    build_PiecewiseFunction(opacity,nmin,nmax,opacity_function)

    color = vtkColorTransferFunction()
    build_ColorTransferFunction(color,nmin,nmax,color_function)

    # Fix in the volume properties (opacity+color)
    volumeProperty = vtkVolumeProperty()
    volumeProperty.SetColor(color)
    volumeProperty.SetScalarOpacity(opacity)
    volumeProperty.ShadeOn()
    volumeProperty.SetInterpolationTypeToLinear()

    # Create the volume actor
    dataVolume = vtkVolume()
    dataVolume.SetProperty(volumeProperty)

    # Choose the mapper to use. Default to texture (faster)
    if mapper=='ray':
        dataVolume.SetMapper(rayMapper)
    else:
        dataVolume.SetMapper(textureMapper)

    # Put an outline around it
    outline = vtkOutlineFilter ()
    outline.SetInput (reader.GetOutput ())
    outline_map = vtkPolyDataMapper ()
    outline_map.SetInput(outline.GetOutput())
    outline_act = vtkActor ()
    outline_act.SetMapper(outline_map)
    outline_act.GetProperty().SetColor(outline_color)

    # Let's use Prabhu's great ivtk to see the thing
    iv = ivtk.viewer()
    camera = iv.renwin.GetActiveCamera()
    camera.SetPosition (-25,-50,30)
    camera.SetViewUp (0,.3,.95)
    iv.renwin.set_background(background_color)
    iv.AddActors([dataVolume,outline_act])

#******************** End of file <vtkutils.py> ****************************
"""Classes for visualization of 4d scalar data using MayaVi.
"""

__author__ = "Fernando Perez <[EMAIL PROTECTED]>"


#*****************************************************************************
# Needed modules
import sys,struct,types,time,shutil,os
try:
    import HTMLgen as html
except ImportError:
    print "Warning: no HTML generation available"
    
import mayavi,gracePlot,pyvtk
from Numeric import *
from vtkpython import *
from Scientific.Statistics.Histogram import Histogram

# my own generic stuff
from IPython.genutils import *
from IPython.numutils import *
import vtkutils

# stuff from modules
ivtk = mayavi.ivtk

#*****************************************************************************
class Data4d:
    """Class for visualizing 4-dimensional scalar data.

    It is meant to be subclassed by classes which implement at least the
    constructor with appropriate routines for reading the data files.
    """
    
    CameraPosition = (-25,-50,30)
    CameraViewUp =  (0,.3,.95)

    def __init__(self):
        raise NotImplementedError
    
    def cut_slice(self,dir,N):
        """Slice a data set along direction dir at N.

        Note that vtk datasets use the same (odd) convention as topological
        density files: data is indexed 'in reverse':

        data(x,y,z,t) = data_arr[t,z,y,x].

        For this reason the slicing below looks 'backward'."""

        if dir == 'x':
            self.slice = self.data[:,:,:,N]
        elif dir == 'y':
            self.slice = self.data[:,:,N,:]
        elif dir == 'z':
            self.slice = self.data[:,N,:,:]
        elif dir == 't':
            self.slice = self.data[N,:,:,:]
        else:
            raise ValueError,'direction must be one of x,y,z,t.'

        self.slice_dir = dir
        self.slice_val = N
        self.axes_labels = qw('x y z t')
        self.axes_labels.remove(dir)

        # compute the histogram for the 3d slice
        self.histo3d = Histogram(ravel(self.slice),100)

    def write_text_file(self,fname):
        """Dump the 4d data to a simple text file."""

        outfile = file(fname,'w')
        nx,ny,nz,nt = self.nx,self.ny,self.nz,self.nt
        print >> outfile, 'nx',nx
        print >> outfile, 'ny',ny
        print >> outfile, 'nz',nz
        print >> outfile, 'nt',nt
        for it in range (nt):
            for iz in range (nz):
                for iy in range (ny):
                    for ix in range (nx):
                        print >> outfile, ix,iy,iz,it,self.data[it,iz,iy,ix]

    def reset(self):
        """Reset the histogram clipping"""
        self.cut_slice(self.slice_dir,self.slice_val)

    def histo3_plot(self,plotter=None):
        """Print min/max data and plot a histogram of the current 3-slice."""

        histo = self.histo3d
        if self.clipped:
            type = 'Clipped'
        else:
            type = 'Unclipped'
        if plotter == None:
            plotter = gracePlot.gracePlot()
        plotter.histoPlot(histo)
        plotter.title('%s=%s %s 3-d slice' %
                      (self.slice_dir,self.slice_val,type) )
        print 'Min:',histo.min,'\nMax:',histo.max

    def histo4_make(self,force=0):
        """Make 4-d histogram."""
        if force:
            try:
                del self.histo4
            except AttributeError: pass
        try:
            self.histo4
        except AttributeError:
            nbins = 250
            print 'Building histograms of full dataset, this may take a moment...',
            sys.stdout.flush()
            self.histo4 = Histogram(ravel(self.data),nbins)
            print
        
        
    def histo4_plot(self,plotter=None):
        """Plot the 4-d histograms.
        
        If they haven't been made, it builds them automatically."""

        self.histo4_make()
        
        plotter = gracePlot.gracePlot()

        plotter.title('4-d data set')
        plotter.histoPlot(self.histo4)

    def clip_slice(self,min_c=None,max_c=None):
        self.min_c = min_c or self.histo3d.min
        self.max_c = max_c or self.histo3d.max
        self.slice = clip(self.slice,self.min_c,self.max_c)
        self.histo3d = Histogram(ravel(self.slice),100)
        self.clipped = 1


    def clip_all(self,min_c=None,max_c=None,fraction=None):
        """Clip all data"""

        self.histo4_make()
        if fraction is None:
            min_c = min_c or self.histo4.min
            max_c = max_c or self.histo4.max
            self.data = clip(self.data,min_c,max_c)
        else:
            # this should find the cuts in the histogram where the data is a
            # certain fraction of the total and then do the actual clipping
            raise NotImplementedError
        
            hist_bins = self.histo4.array[:,0]
            hist_counts = self.histo4.array[:,1]
            count_max = max(self.histo4.array[:,1])
            count_min = fraction * count_max
            
            max_c = self.histo4.max
            min_c = None
        self.histo4_make(force=1)


    def make_vtk(self):
        """Build a vtk data obj from the clipped 3d slice."""
        dims = list(self.slice.shape)
        # We need to give the dims as (x,y,z), not (z,y,x):
        dims.reverse()
        grid = pyvtk.StructuredPoints(dims)

        # RectilinearGrid is another option for this [use (x,y,z) order]:
        #grid = pyvtk.RectilinearGrid(range(dims[2]),range(dims[1]),range(dims[0]))

        if self.slice.iscontiguous():
            dat = self.slice.flat
        else:
            dat = ravel(self.slice)
        point_data = pyvtk.PointData(pyvtk.Scalars(dat,
                                     'Data Density: '+self.slice_dir+
                                     '='+`self.slice_val`,'default'))
        header = 'Data density slice %s=%s' % (self.slice_dir,self.slice_val)
        return pyvtk.VtkData(grid,header,point_data)

    def make_vtk_vol(self):
        """Build a vtk data obj from the clipped 3d slice."""

        # Define function which can scale density to range nmin, nmax
        densmax=self.histo3d.min
        densmin=self.histo3d.max
        nmin=0
        nmax=255

        dims = list(self.slice.shape)
        # We need to give the dims as (x,y,z), not (z,y,x):
        dims.reverse()
        grid = pyvtk.StructuredPoints(dims)

        # RectilinearGrid is another option for this [use (x,y,z) order]:
        #grid = pyvtk.RectilinearGrid(range(dims[2]),range(dims[1]),range(dims[0]))

        if self.slice.iscontiguous():
            dat = self.slice.flat
        else:
            dat = ravel(self.slice)
            
        # Data must be stored as a byte array for VTK's volume rendering to
        # work.  Is the map(int()) really needed?
        dat = array(map(int,(dat-densmin)/(densmax-densmin)*(nmax-nmin)+nmin),
                         'b')
                         
        point_data = pyvtk.PointData(pyvtk.Scalars(dat,
                                     'Data Density: '+self.slice_dir+
                                     '='+`self.slice_val`,'default'))
        header = 'Data density slice %s=%s' % (self.slice_dir,self.slice_val)
        return pyvtk.VtkData(grid,header,point_data)

    def frac2val(self,frac):
        """Return the actual value of a fraction (0-1) of the data's range.
        """
        return self.histo3d.min + frac*(self.histo3d.max - self.histo3d.min)
    
    def load_isosurface(self,isoval,mode='relative'):
        """Load an isosurface at val (as a fraction of data range)"""
        isosurface = self.mv.load_module('IsoSurface',0)

        if mode=='relative':
            isoval = self.frac2val(isoval)  # turn the fraction into a data value
        else:
            if isoval < self.histo3d.min:
                isoval = self.frac2val(0.01)
            elif isoval > self.histo3d.max:
                isoval = self.frac2val(0.99)

        #print 'ISO val=%s, mode=%s' % (isoval,mode)  # dbg
        isosurface.slider_pos = isoval
        isosurface.normals_on.set(1)
        isosurface.do_normals()
        isosurface.cont_fil.SetValue (0, isoval)
        # call this explicitly (it was already called at construction time) to
        # update the gui with our changes:
        isosurface._gui_init()

    def getLutIndex(self,l, val):
        c = l.GetColor(val)
        for i in range(l.GetNumberOfColors()):
            if l.GetTableValue(i)[:-1] == c:
                return i

    def square_window(self,lut,min_val,max_val,invert=0):
        """In-place modify a lut with a square window.

        It sets alpha outside of min/max values (given as data values) to
        0 and 1 inside. If invert=1, the alpha table is the opposite.
        """
        imin = self.getLutIndex(lut,min_val)
        imax = self.getLutIndex(lut,max_val)

        # Set up table of transparencies
        lut_nvalues = lut.GetNumberOfTableValues()
        alpha = invert * ones(lut_nvalues,'f')
        alpha[imin:imax] = 1-invert

        # Change the lut according to the alpha table
        for i in range(0,lut_nvalues):
            val = lut.GetTableValue (i)
            lut.SetTableValue(i, val[0], val[1], val[2], alpha[i])

    def load_ContourGridPlane(self,axis=0,pos=0,contours=0):
        """Load a ContourGridPlane module at given axis/position.

        - axis: use the convention: x,y,z == 0,1,2

        - pos can be given as -1 for the end of an axis.

        - contours: Total # of contours per plane. If contours is negative,
        then colormaps are shown instead of individual contour lines. This is
        much faster but gives colormaps which 'bleed' out of the isosurfaces,
        since they are 1x1 squares and not smoothed to follow the isosurface
        contours.
        """
        cplane = self.mv.load_module('ContourGridPlane',0)
        cplane.axis_var.set(axis)
        if pos == -1:
            pos = cplane.dim[axis]
        cplane.slider_pos = pos
        if contours > 0:
            cplane.contour_on.set (1)
            cplane.n_cnt.set (contours)
            cplane.do_contour()
        cplane.config_extents() # needed so the plane actually shows

    def box_countour_planes(self,contours,invert=0,mode='relative'):
        """Put contour planes around the 3d box with a clipped colormap.

        They are loaded in a separate module manager.

        Parameters:

        - contours: Total # of contours per plane. If contours is negative,
        then colormaps are shown instead of individual contour lines. This is
        much faster but gives colormaps which 'bleed' out of the isosurfaces,
        since they are 1x1 squares and not smoothed to follow the isosurface
        contours.
        
        - invert: whether to invert the lookup table.
        """
        
        self.add_cutoff_lut_mod_manager(self.iso_val0,self.iso_val1,
                                        invert,mode)
        if contours>0:
            print 'Building contour box. This will take a while...'
        for dir in range(3):
            self.load_ContourGridPlane(dir,0,contours)
            self.load_ContourGridPlane(dir,-1,contours)

    def add_cutoff_lut_mod_manager(self,min_val,max_val,invert=0,
                                   mode='relative'):
        """Add a module manager with a modified lut for transparency.
        """

        #print 'cutoff lut. min=%s, max=%s, invert=%s' % \
        #      (min_val,max_val,invert)  # dbg
        # new module manager.
        self.mv.load_mm('new')
        dvm = self.mv.get_current_dvm()
        mm = dvm.get_current_module_mgr()

        lh = mm.scalar_lut_h # get the scalar_lut_handler (LutHandler)
        lut = lh.lut # get the vtkLookupTable

        if mode=='relative':
            min_val = self.frac2val(min_val)
            max_val = self.frac2val(max_val)
        #print 'min %s max %s ' % (min_val, max_val)  # dbg
        self.square_window(lut,min_val,max_val,invert)


    def view(self,iso_val0=0.25,iso_val1=None,check_histo=1,contours=-1,
             invert=1,iso_mode='relative',legend=1):
        """Visualize in MayaVi the currently active slice.

        Parameters:

        - iso_val0: lower value of the isosurface cut plane.
        - iso_val1: higher value of the isosurface cut plane.
        - check_histo: show histogram to check for data clipping or not.
        - contours: how many contours to show (0 disables).
        - invert: whether the contour planes should use an inverted LUT.
        - iso_mode: 'relative'/'absolute'. Whether isosurfaces are specified
        as [0,1] relative values or as absolute data values. """

        # relative_3 and relative are the same for this function (which
        # doesn't know about the 4d data)
        if iso_mode.startswith('relative'):
            iso_mode = 'relative'
        self.iso_val0 = iso_val0
        if iso_val1 is None:
            if iso_mode == 'relative':
                iso_val1 = 1 - iso_val0
            else:
                raise ValueError,'in absolute mode you must give max value'
        self.iso_val1 = iso_val1

        if check_histo:
            plotter = gracePlot.gracePlot()
        while check_histo:
            self.histo3_plot(plotter)
            do_clip = raw_input('\nClip values [N]? ')
            if do_clip.lower() != 'y':
                break
            low = float(raw_input('Low ['+`self.histo3d.min`+'] = ') or
                        self.histo3d.min)
            high = float(raw_input('High ['+`self.histo3d.max`+'] = ') or
                         self.histo3d.max)
            self.clip_slice(low,high)

        fname3d = '%s.%s%s.vtk' % (self.fname,self.slice_dir,self.slice_val)
        self.fname3d = fname3d
        self.vtkdata = self.make_vtk()
        self.vtkdata.tofile(fname3d)
        # The config option turns on/off showing a GUI control
        self.mv = mayavi.mayavi()
        self.mv.open_vtk(fname3d,config=0)

        # Let's not clutter up with all these temp files
        os.unlink(fname3d)

        # Now load modules and configure some by hand:

        # Mayavi bug: load the isosurfaces *first*, otherwise window repainting
        # breaks
        self.load_isosurface(iso_val0,iso_mode)
        self.load_isosurface(iso_val1,iso_mode)

        self.mv.load_module('Outline',0)

        axes = self.mv.load_module('Axes',0)
        for axis,label,i in zip(qw('X Y Z'),self.axes_labels,range(3)):
	    apply (eval ("axes.axes.Set%sLabel"%(axis)), (label,))
            axes.axis_txt_var[i].set(label)

        loc = self.mv.load_module('Locator',0)
        loc.axes.SetOrigin([0,0,0])
        loc.coord_var.set([0,0,0])

        # Rotate view to a nice position and angle
        view = Data4d.CameraPosition + Data4d.CameraViewUp # join tuples
        self.mv.renwin.update_view(*view)

        # add a legend
        if legend:
            dvm = self.mv.get_current_dvm()
            mm = dvm.get_current_module_mgr ()
            slh = mm.get_scalar_lut_handler ()
            # turn legend on
            slh.legend_on.set(1)  # set to 1/0 for on/off
            slh.legend_on_off ()  # this _must_ be called to apply change

        # Contour planes. Expensive if contours > 0, cheap if <0.
        if contours:
            self.box_countour_planes(contours,invert,iso_mode)

    def save_png(self,dir='',fname=None,size=None):
        """Save snapshot as a png file. Give name without extension.
        
        If no filename is given, the name of the 3-d slice is used.

        If size is given, convert is called with 'convert -geometry size
        fname' (so it does an in-place rewrite of the file to the given size
        spec.  """
        
        if fname is None:
            fname = self.fname3d.replace('vtk','png')
        elif fname.endswith('.png'):
            pass
        else:
            fname = fname +'.png'
        fname = os.path.join(dir,fname)
        #print '***png file',fname  # dbg
        self.mv.renwin.save_png(fname)
        if size is not None:
            xsys('convert -geometry %s %s %s' % (size,fname,fname))

    def save_eps(self,dir='',fname=None,zip=0):
        """Save snapshot as a PostScript file. Give name without extension.

        By default the file is gzipped, use zip=0 to prevent this."""
        
        if fname is None:
            fname = self.fname3d.replace('vtk','eps')
        elif fname.endswith('.eps'):
            pass
        else:
            fname = fname +'.eps'
        fname = os.path.join(dir,fname)
        print '***ps file',fname  # dbg
        self.mv.renwin.save_ps(fname)
        # fix this file and zip it
        tmp_file = os.path.join(dir,'__tmp.eps')
        xsys('eps2eps %s %s' % (fname,tmp_file),verbose=1)
        os.rename(tmp_file,fname)
        if zip:
            xsys('gzip -f %s' % fname)

    def volume(self,mapper='texture',dsample=1,opacity=None):
        """Show volumetric density.

        Call with mapper='ray' for using ray tracer instead of texture mapper.

        dsample: distance between samples for ray tracer. If <1 it gets slow.

        opacity: list of (x,y) pairs for opacity function (see vtkutils.py).
        If unspecified, it defaults to the instance's opacity table
        (self.opacity). Absent that, to a builtin default.
        """
        
        # Defaults
        if opacity is not None:
            opacity_function = opacity
        elif hasattr(self,'opacity'):
            opacity_function = self.opacity
        else:
            opacity_function = vtkutils.opacity_fns.v3

        fname3d_vol = '%s.%s%s.vol.vtk' % (self.fname,self.slice_dir,self.slice_val)
        self.vtkdata_vol = self.make_vtk_vol()
        self.vtkdata_vol.tofile(fname3d_vol)
        vtkutils.volume_render(fname3d_vol,mapper=mapper,
                               opacity_function=opacity_function,
                               color_function=vtkutils.color_fns.blue_red,
                               background_color=(1,1,1))

    def slice_table(self,dir='x',step=1,fname=None,
                    iso_val0=0.25,iso_val1=None,iso_mode='relative',
                    contours=-1,invert=1,
                    out_dir='',image_size=310,make_eps=0):
        """Make an HTML page with a table of 3-d slices.

        Use iso_mode = 'relative_3' for each 3d slice to have its own
        isosurfaces computed with values relative to the its histogram only
        (and not to the full 4d histogram).
        """

        if fname is None:
            fname = os.path.basename(self.fname)
        html_dir = os.path.join(out_dir,fname+'.slices')
        try:
            os.mkdir(html_dir)
        except:
            pass

        # for the full table, reset the iso_values so that they are relative
        # to the data range ends, but measured on the full 4d dataset. Once
        # the values are determined, then the mode should become absolute
        if iso_mode=='relative':
            self.histo4_make()
            h4 = self.histo4
            if iso_val1 is None:
                iso_val1 = 1 - iso_val0
            iso_val0 = h4.min + iso_val0*(h4.max-h4.min)
            iso_val1 = h4.min + iso_val1*(h4.max-h4.min)
            iso_mode = 'absolute'
            
        # Build the slice table, which can fail for various reasons (bad or
        # zero data, typically).
        images = []
        dim = dict(zip(qw('t z y x'),self.data.shape))
        start = time.time()
        for slice in range(0,dim[dir],step):
            try:
                self.cut_slice(dir,slice)
            except:
                warn('Slice %s could not be generated. \n'
                     'Removing HTML dir %s and exiting slice_table().'
                     % (slice, html_dir))
                shutil.rmtree(html_dir)
                return
            else:
                self.view(iso_val0=iso_val0,iso_val1=iso_val1,iso_mode=iso_mode,
                          check_histo=0,contours=contours,invert=invert)
                pngfile = os.path.basename(self.fname3d.replace('.vtk','.png'))
                if make_eps:
                    epsfile = pngfile.replace('.png','.eps')
                    self.save_eps(html_dir,epsfile)
                self.save_png(html_dir,pngfile,image_size)
                images.append(['%s=%s'%(dir,slice),pngfile])
                self.mv.quit() # don't litter with open mayavi instances
        rtime = time.time() - start
        print 'Rendering time: %4.2f seconds for %s slices.' % \
              (rtime ,len(images) )

        # If we didn't fail in the above, proceed with the HTML generation
        
        doc = html.SimpleDocument(title=fname)
        doc.append(html.Heading(1,fname,align='center'))

        # now make a table with the images
        tab = html.Table('Slices along %s direction' % dir,
                         column1_align='center',cell_align='center')
        tab.body = []
        col = 1; l1 = []; l2 = []
        for img in images:
            #print 'col',col,'image',img  # dbg
            l1.append(html.Image(img[1]))
            l2.append(img[0])
            if col%3:
                col += 1
            else:
                #print 'finished row' # dbg
                tab.body.extend([l1,l2])
                col = 1; l1 = []; l2 = []
        if col>1:
            tab.body.extend([l1,l2]) # don't forget last (incomplete) row
        doc.append(tab)
        doc.write(os.path.join(html_dir,'index.html'))

#---------------------------------------------------------------------------    
class Eigenmode(Data4d):
    def __init__(self,fname,mode_num,slice_dir='x',slice_val=0):
        """Store and visualize fermionic eigenmode information.

        The main data attributes are:
        data_p1: <psi_bar*(gamma_5+1)*psi>
        data_p2: <psi_bar*(gamma_5-1)*psi>
        data_chi: (p1+p2)/2 = <psi_bar*gamma_5*psi>
        data_psi: (p1-p2)/2 = <psi_bar*psi>

        The default data attribute is initialized to data_chi.
        """

        self.read(fname,mode_num)
        self.data = self.data_chi  # by default plot the chiral density
        self.fname = fname
        self.mode_num = mode_num
        self.cut_slice(slice_dir,slice_val) # creates an actual 3d slice
        self.clipped = 0
        self.opacity = [(0,1),(0.3,0.8),(1,0)] # for volume rendering

    def view(self,type=None,slice_dir=None,slice_val=None,
             iso_val0=0.25,iso_val1=None,check_histo=1,
             contours=-1,invert=0,iso_mode='relative_3',legend=1):
        """View either chirality(+/-), chiral density or fermion density.

        Specify type as 'p1','p2','chi' or 'psi'.
        If no type is specified, the currently active slice is viewed."""

        new_slice = 0
        if slice_dir is None:
            slice_dir = self.slice_dir
            new_slice = 1
        if slice_val is None:
            slice_val = self.slice_val
            new_slice = 1
        if type is not None:
            data = 'data_' + type
            self.data = getattr(self,data)
            new_slice = 1
        if new_slice:
            self.cut_slice(slice_dir,slice_val)
        Data4d.view(self,iso_val0,iso_val1,check_histo,contours,invert,
                    iso_mode,legend)


    def read(self,fname,mode_num):
        """Read in a text density file.
        Assumes nx=ny=nz (but not necessarily =nt)."""

        for line in file(fname):
            if line.startswith('nx'):
                nx = ny = nz = int(line.strip().split()[1])
            if line.startswith('nt'):
                nt = int(line.strip().split()[1])
                break

        print 'Reading data with nx=%s nt=%s' % (nx,nt)

        # a -> g5+1
        cmd = "awk '/^%sPG5P/ { print $6}' %s" % (mode_num,fname)
        tfile = os.popen(cmd)
        data_p1_flat = array(map(float,tfile))
        data_p1_flat.shape = (nt,nz,ny,nx)

        # b -> g5-1
        cmd = "awk '/^%sPG5P/ { print $7}' %s" % (mode_num,fname)
        tfile = os.popen(cmd)
        data_p2_flat = array(map(float,tfile))
        data_p2_flat.shape = (nt,nz,ny,nx)

        with(self,nx=nx,ny=ny,nz=nz,nt=nt,
             data_p1=data_p1_flat,
             data_p2=data_p2_flat,
             data_chi = 0.5*(data_p1_flat+data_p2_flat),
             data_psi = 0.5*(data_p1_flat-data_p2_flat),)
        data_size = nx*ny*nz*nt
        print 'Read',data_size,'data points. Shape:',(nx,ny,nz,nt)

    def histo4_all_make(self):
        """Make histograms of the full 4d data.

        Makes histo4_<type>, where <type> is in [p1,p2,chi,psi].

        You can then view them with histo4_plot."""

        try:
            self.histo4_all_made
        except AttributeError:
            pass
        else:
            return
        
        nbins = 150
        print 'Building histograms of full dataset, this may take a moment...',
        sys.stdout.flush()
        try:
            self.histo4_p1 = Histogram(ravel(self.data_p1),nbins)
        except:
            warn("Error making p1 histogram")
        try:
            self.histo4_p2 = Histogram(ravel(self.data_p2),nbins)
        except:
            warn("Error making p2 histogram")
        try:
            self.histo4_chi = Histogram(ravel(self.data_chi),nbins)
        except:
            warn("Error making chi histogram")
        try:
            self.histo4_psi = Histogram(ravel(self.data_psi),nbins)
        except:
            warn("Error making psi histogram")

        self.histo4_all_made = 1
        print 'Done.'
        

    def histo4_all_plot(self,plotter=None):
        """Plot the 4-d histograms.
        
        If they haven't been made, it builds them automatically."""

        self.histo4_all_make()
            
        plotter = gracePlot.gracePlot()
        plotter.multi(2,2,0.1,0.2,0.4)  # 2x2 plot table

        plotter.title('p1: <psi_b(g5+1)psi>')
        if hasattr(self,'histo4_p1'):
            plotter.histoPlot(self.histo4_p1)
        plotter.focus(0,1)
        plotter.title('p2: <psi_b(g5-1)psi>')
        if hasattr(self,'histo4_p2'):
            plotter.histoPlot(self.histo4_p2)
        plotter.focus(1,0)
        plotter.title('chi: <psi_b*g5*psi>')
        if hasattr(self,'histo4_chi'):
            plotter.histoPlot(self.histo4_chi)
        plotter.focus(1,1)
        plotter.title('psi: <psi_b*g5*psi>')
        if hasattr(self,'histo4_psi'):
            plotter.histoPlot(self.histo4_psi)

    def slice_table(self,type='chi',dir='x',step=1,
                    iso_val0=0.25,iso_val1=None,iso_mode='relative_3',
                    contours=-1,invert=0,
                    out_dir='',image_size=350,make_eps=0):
        """Make an HTML page with a table of 3-d slices.
        """

        data = 'data_' + type
        self.data = getattr(self,data)
        fname = os.path.basename(self.fname) + \
                '_mode_%s_%s' % (self.mode_num,type)
        Data4d.slice_table(self,dir,step,fname,iso_val0,iso_val1,iso_mode,
                           contours,invert,
                           out_dir,image_size,make_eps)

#---------------------------------------------------------------------------
class TopologicalDensity(Data4d):
    def __init__(self,fname,slice_dir='x',slice_val=0):
        self.read(fname)
        self.fname = fname
        self.cut_slice(slice_dir,slice_val)
        self.clipped = 0

    def read(self,fname):
        """Read in a topological density file.

        Does NOT do byte-reversal.
        """

        # MILC code:
        TOPO_VERSION = 66051
        # of bytes to skip in file *from the beginning* before data starts
        offset = 108

        topo = open(fname)
        topo_version,nx,ny,nz,nt = struct.unpack('i'*5,topo.read(5*4))
        if topo_version != TOPO_VERSION:
            warn('Wrong topological density version number (or '
                 'byte-reversed file)!\n'
                 'Topo_version = %s, should be %s' %
                 (topo_version,TOPO_VERSION) , level=4)
        topo.seek(offset)
        data_size = nx*ny*nz*nt*4  # these files use C float, not double
        topo_flat = fromstring(topo.read(data_size),Float32)
        topo_flat.shape = (nt,nz,ny,nx)
        with(self,nx=nx,ny=ny,nz=nz,nt=nt,data=topo_flat)
        print 'Read',data_size/4,'data points. Shape:',(nx,ny,nz,nt)

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys - and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
MayaVi-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mayavi-users

Reply via email to