## Automatically adapted for numpy.oldnumeric Jul 23, 2007 by 

#############################################################################
#
# Author: Anna Omelchenko
#
# Copyright: M. Sanner TSRI
#
#############################################################################

#
#$Header: /opt/cvs/Volume/Pvv/getsubvolCommands.py,v 1.3 2007/07/24 17:30:45 vareille Exp $
#
#$Id: getsubvolCommands.py,v 1.3 2007/07/24 17:30:45 vareille Exp $
#
""" Module implements a command to get a subvolume from a displayed volume and
to load it in to the viewer as a separate volume.
GUI description:
    Cropping box (CB) is used for selecting the subvolume size:
        'Control' + mouse buttons 1,2,3 are used to change the CB size
        along the X, Y, Z axes, respectively;
        'meta' + mouse button 3 changes the CB size in all directions;
        'meta' + button 1 moves the CB inside the volume.
        
    Press ' >> ' button to get a subvolume,
    Press ' << ' button to return to the original volume.
    Press 'volume outline' checkbutton to display the outline
    of the original (parent) volume.
    To switch to another parent volume use 'Dismiss' button followd by
    'Get Subvolume' from the main menu. """
    

from ViewerFramework.VFCommand import Command, CommandGUI
from mglutil.gui.InputForm.Tk.gui import InputFormDescr
import Tkinter, struct, string
from time import time
import numpy.oldnumeric as Numeric
from DejaVu.Box import Box
volGeom = None

class GetSubvolume(Command):

    def __init__(self):
        Command.__init__(self)
        self.isDisplayed=0

    def onAddCmdToViewer(self):
        """Called when the command is added(loaded) to the viewer"""
        cropBox = volGeom.cropBox
        self.firstUse = 1
        self.rootData = None
        cropBox.callbackFunc.append(self.updateInfo)
        self.form = None
        self.main_box = None
        self.parvol_size = (0,0,0)
        self.fixvolume = 0
        self.bounds_list = []

    def updateInfo(self, crop):
        """ Updates the entries on the input form when the size of
        the crop box is changed. """
        if self.form==None: return
        ent = self.form.descr.entryByName
        ent['wx']['widget'].configure(text=crop.dx)
        ent['wy']['widget'].configure(text=crop.dy)
        ent['wz']['widget'].configure(text=crop.dz)
        ent['minx']['widget'].configure(text=crop.xmin)
        ent['maxx']['widget'].configure(text=crop.xmax)
        ent['miny']['widget'].configure(text=crop.ymin)
        ent['maxy']['widget'].configure(text=crop.ymax)
        ent['minz']['widget'].configure(text=crop.zmin)
        ent['maxz']['widget'].configure(text=crop.zmax)

        if len(self.bounds_list)>1:
            ent['mvout']['widget'].configure(state=Tkinter.NORMAL)
        else:
            ent['mvout']['widget'].configure(state=Tkinter.DISABLED)



    def guiCallback(self):
        """Creates the input form (GUI). Called every time the 'Get Subvolume'
        button is pressed"""
        
        if not volGeom.volume: return
        if self.isDisplayed: return
        
        from SimpleDialog import SimpleDialog
        
        root = Tkinter.Toplevel()
        root.withdraw()
        ans = SimpleDialog(root, text="Make current volume the parent volume?",
                           buttons=["Yes", "No", "Cancel"],
                           default=0,
                           title="switch dialog").go()
        root.destroy()
        if ans == 1 and self.form:
            self.idf.form.deiconify()
            self.fixvolume = 0
            self.isDisplayed=1
        elif ans == 1 and self.form == None:
            return
        elif ans == 2:
            return
        elif ans == 0:
            self.isDisplayed=1
            self.fixvolume = 1
            x,y,z = volGeom.volume.GetSize()
            self.parvol_size = (x,y,z)
            self.rootData = self.getDataArr(x, y, z)
            self.bounds_list = [(0, x, 0, y, 0, z)]
            if self.form:
                self.form.destroy()
            self.buildForm()
            
            if self.main_box:
                self.change_boxsize()
            #self.vf.resetUndo()

    def getDataArr(self, x, y, z):
        """Returns an array of volume data  """
        #Has to be implemented by a derived class
        pass
    
    def buildForm(self):
        """Adds widgets to the input form"""
        idf = self.idf = InputFormDescr(title = 'GetSubvolume')
        crop = volGeom.cropBox 
        wx, wy, wz = crop.dx, crop.dy, crop.dz
        xmin, ymin, zmin = crop.xmin, crop.ymin, crop.zmin
        xmax, ymax, zmax = crop.xmax, crop.ymax, crop.zmax

        labelFont = '-*-Helvetica-Bold-R-Normal-*-*-180-*-*-*-*-*-*'
        numberFont = '-*-Helvetica-Bold-R-Normal-*-*-160-*-*-*-*-*-*'

        idf.append({'widgetType':Tkinter.Label,
                    'wcfg':{'text':'width:  ', 'font':labelFont},
                    'gridcfg':{'column':0, 'sticky':'w'}})
        
        col = '#000000'
        idf.append({'widgetType':Tkinter.Label,
                    'name':'wx',
                    'wcfg':{'text':str(wx), 'fg':col,
                            'font':numberFont},
                    'gridcfg':{'column':1,'row':-1,'sticky':'e'}})
        idf.append({'widgetType':Tkinter.Label,
                    'name':'wy',
                    'wcfg':{'text':str(wy), 'fg':col,
                            'font':numberFont},
                    'gridcfg':{'column':2,'row':-1,'sticky':'e'}})
        idf.append({'widgetType':Tkinter.Label,
                    'name':'wz',
                    'wcfg':{'text':str(wz), 'fg':col,
                            'font':numberFont},
                    'gridcfg':{'column':3,'row':-1,'sticky':'e'}})
        
        idf.append({'widgetType':Tkinter.Label,
                    'wcfg':{'text':'min  :  ','font':labelFont},
                    'gridcfg':{'column':0,'sticky':'w'}})
        idf.append({'widgetType':Tkinter.Label,
                    'name':'minx',
                    'wcfg':{'text':str(xmin),
                            'font':numberFont},
                    'gridcfg':{'column':1,'row':-1,'sticky':'e'}})
        idf.append({'widgetType':Tkinter.Label,
                    'name':'miny',
                    'wcfg':{'text':str(ymin),
                            'font':numberFont},
                    'gridcfg':{'column':2,'row':-1,'sticky':'e'}})
        idf.append({'widgetType':Tkinter.Label,
                    'name':'minz',
                    'wcfg':{'text':str(zmin),
                            'font':numberFont},
                    'gridcfg':{'column':3,'row':-1,'sticky':'e'}})
        
        idf.append({'widgetType':Tkinter.Label,
                    'wcfg':{'text':'max  :  ', 'font':labelFont},
                    'gridcfg':{'column':0, 'sticky':'w'}})
        idf.append({'widgetType':Tkinter.Label,
                    'name':'maxx',
                    'wcfg':{'text':str(xmax),
                            'font':numberFont},
                    'gridcfg':{'column':1,'row':-1,'sticky':'e'}})
        idf.append({'widgetType':Tkinter.Label,
                    'name':'maxy',
                    'wcfg':{'text':str(ymax),
                            'font':numberFont},
                    'gridcfg':{'column':2,'row':-1,'sticky':'e'}})
        idf.append({'widgetType':Tkinter.Label,
                    'name':'maxz',
                    'wcfg':{'text':str(zmax),
                            'font':numberFont},
                    'gridcfg':{'column':3,'row':-1,'sticky':'e'}})

        idf.append({'widgetType':Tkinter.Button,
                    'name':'mvin',
                    'wcfg':{'borderwidth':2,
                            'text':' >> ',
                            'disabledforeground':'#CCCCCC',
                            'font': labelFont},
                    'gridcfg':{'column':0, 'sticky':'ew'},
                    'command': self.mvin_cb
                    })
        
        idf.append({'widgetType':Tkinter.Button,
                    'name':'mvout',
                    'wcfg':{'borderwidth':2,
                            'text':' << ',
                            'disabledforeground':'#CCCCCC',
                            'font': labelFont},
                    'gridcfg':{'column':2, 'row':-1,
                               'columnspan':2,'sticky':'ew'},
                    'command': self.mvout_cb
                    })
        self.checkvar = Tkinter.IntVar()
        self.checkvar.set(0)
        idf.append({'widgetType':Tkinter.Checkbutton,
                    'name':'switch',
                    'wcfg':{'text':'volume outline',
                            'variable':self.checkvar,
                            'font':numberFont},
                    'gridcfg':{'column': 0, 'columnspan':4,
                               'sticky':'w'},
                    'command':self.switch_box
                    })
        idf.append({'widgetType':Tkinter.Button,
                    'name':'reset_crop',
                    'wcfg':{'borderwidth':2, #'width':10,'height':2,
                            'text':'Reset Crop',
                            'font': labelFont},
                    'gridcfg':{'column':0, 'columnspan':4, 'sticky':'ew'},
                    'command': self.resetCrop_cb
                    })        

        idf.append({'widgetType':Tkinter.Button,
                    'name':'dismiss',
                    'wcfg':{'borderwidth':2, #'width':10,'height':2,
                            'text':'Dismiss',
                            'font': labelFont},
                    'gridcfg':{'column':0, 'columnspan':4, 'sticky':'ew'},
                    'command': self.dismiss
                    })        
        self.form = self.vf.getUserInput(idf, modal = 0, blocking=0)
        ent = self.form.descr.entryByName
        if len(self.bounds_list)>1:
            ent['mvout']['widget'].configure(state=Tkinter.NORMAL)
        else:
            ent['mvout']['widget'].configure(state=Tkinter.DISABLED)

    def dismiss(self):
        """Dismisses the input form. Called when 'Dismiss' button is
        pressed """
        self.idf.form.withdraw()
        self.isDisplayed = 0

    
    def readVox(self, filename):
        """ Reads and returns data from .vox file"""
        f = open(filename)
        data = f.readlines()
        f.close()
        volsize = 0
        voxsize = 0
        endian = None
        i = 0
        while(1):
            i = i+1
            if data[i]=='##\014\012': break
        while(1):
            i = i+1
            if data[i]=='##\014\012': break
            else:
                s = string.split(data[i])
                if s[0] == "VolumeSize":
                    volsize = int(s[1])*int(s[2])*int(s[3])
                elif s[0] == "VoxelSize":
                    voxsize = int(s[1])
                elif s[0] == "Endian":
                    if s[1] == 'B': endian = '>'
                    else : endian = '<'
        s = reduce( lambda x,y: x+y, data[i+1:] )
        #print "type:", type(s)
        #print "len(s):", len(s)
        #print "volsize:", volsize, "voxsize:", voxsize, "endian:", endian
        if voxsize == 8:
            # 'B' - unsigned char
            fmt = "%s%dB"%(endian, volsize)
        elif voxsize == 16:
            # 'H' - unsigned short
            fmt = "%s%dH"%(endian, volsize)
        values = struct.unpack( fmt, s)
        return (values, voxsize)

    def mvin_cb(self):
        """called when '>>' button is pressed (to get a new
        subvolume)"""
        
        crop = volGeom.cropBox 
        cropbounds = (crop.xmin, crop.xmax,
                      crop.ymin, crop.ymax,
                      crop.zmin, crop.zmax)
        self.doitWrapper(cropbounds, 'mvin', fixvolume = self.fixvolume)


    def mvout_cb(self):
        """called when '<<' button is pressed (to return to original volume)"""
        
        crop = volGeom.cropBox
        cropbounds = (crop.xmin, crop.xmax,
                      crop.ymin, crop.ymax,
                      crop.zmin, crop.zmax)
        self.doitWrapper(cropbounds, 'mvout', fixvolume = self.fixvolume)

    def __call__(self, cropbounds, action, fixvolume = 1, **kw):
        """None <- GetSubvolume(cropbounds, action, fixvolume = 1)
        cropbounds : bounds of the cropping box(subvolume):
                  a tuple(Xmin, Xmax, Ymin, Ymax, Zmin, Zmax);
        action : can be either 'mvin' to get a subvolume or
                 'mvout' to return to original(parent) volume;
        fixvolume: if set to 1 the current volume becomes the
                   original(parent) volume """

        if len(cropbounds)!= 6:
            raise ValueError("Wrong number of cropbounds; expected 6, got %d."\
                             %len(cropbounds))
        for cb in cropbounds:
            if cb < 0:
                raise ValueError("cropbounds should be positive values")
        size = volGeom.volume.GetSize()
        if cropbounds[1] > size[0]:
            cropbounds = list(cropbounds)
            cropbounds[1]=size[0]
        elif cropbounds[1] < cropbounds[0]:
            raise ValueError("Xmax must be greater then Xmin")
        if cropbounds[3] > size[1]:
            cropbounds = list(cropbounds)
            cropbounds[3] = size[1]
        elif cropbounds[3] < cropbounds[2]:
            raise ValueError("Ymax must be greater then Ymin")
        if cropbounds[5] > size[2]:
            cropbounds = list(cropbounds)
            cropbounds[5] = size[2]
        elif cropbounds[5] < cropbounds[4]:
            raise ValueError("Zmax must be greater then Zmin")
        if fixvolume:
            vx, vy, vz = volGeom.volume.GetSize()
            self.bounds_list = [(0, vx, 0, vy, 0, vz)]
            self.parvol_size = (vx,vy,vz)
            self.rootData = self.getDataArr(vx,vy,vz)
            #print "rootdata shape:", self.rootData.shape
            if self.main_box:
                self.change_boxsize()
            #self.vf.resetUndo()
        else :
            if len(self.bounds_list) == 0:
                print "Parent volume is not set..."
                return
        #self.doitWrapper(cropbounds, action, fixvolume)
        apply(self.doitWrapper, (cropbounds, action, fixvolume),kw)

    def doit(self, cropbounds, action, fixvolume, **kw):
        """Gets a subvolume, loads it as a new volume into the viewer"""
        
        if action == 'mvin':
            bounds = self.bounds_list[-1]
            xo, yo, zo = bounds[0], bounds[2], bounds[4]
            bounds = ( xo+cropbounds[0], xo+cropbounds[1],
                       yo+cropbounds[2], yo+cropbounds[3],
                       zo+cropbounds[4], zo+cropbounds[5] )
            box = ( 0, bounds[1]-bounds[0],
                    0, bounds[3]-bounds[2],
                    0, bounds[5]-bounds[4] )

            self.bounds_list.append(bounds)

        elif action == 'mvout':
            bounds = self.bounds_list[-2]
            currbounds = self.bounds_list[-1]
            xo = currbounds[0] - bounds[0]
            yo = currbounds[2] - bounds[2]
            zo = currbounds[4] - bounds[4]
            box = ( xo + cropbounds[0],  xo + cropbounds[1],
                    yo + cropbounds[2],  yo + cropbounds[3],
                    zo + cropbounds[4],  zo + cropbounds[5] )
            self.bounds_list.pop()
        
        dx = bounds[1]-bounds[0]
        dy = bounds[3]-bounds[2]
        dz = bounds[5]-bounds[4]
        #t1 = time()
        if len(self.bounds_list) == 1:
            narr = self.rootData
        else:
##          narr = Numeric.array(Numeric.transpose\
##                               (self.rootData[ bounds[0]:bounds[1],
##                                               bounds[2]:bounds[3],
##                                               bounds[4]:bounds[5] ]))
            narr = Numeric.array (self.rootData[ bounds[4]:bounds[5],
                                                 bounds[2]:bounds[3],
                                                 bounds[0]:bounds[1]])
        #t2 = time()
        #self.narr = narr
        #print "time to create an array: %.2f"%(t2-t1,)
        self.createNewVolume(narr, dx, dy, dz)
        volGeom.cropBox.setSize(box[0], box[1], box[2], box[3], box[4], box[5])
        volGeom.cropBox.update()
        if self.main_box:
            self.update_box()
        if fixvolume: self.fixvolume = 0

    def createNewVolume(self, narr, dx, dy, dz):
        """Creates new volume object and adds it to the viewer."""
        #Has to be implemented by a derived class.""
        pass
    
##      def setupUndoAfter(self, cropbounds, action, fixvolume, **kw):
##          if action == "mvout":
##              undoaction = "mvin"
##          elif action == "mvin":
##              undoaction = "mvout"
##          cropBox = volGeom.cropBox
##          undobounds = (cropBox.xmin, cropBox.xmax,
##                        cropBox.ymin, cropBox.ymax,
##                        cropBox.zmin, cropBox.zmax)
##          self.addUndoCall((undobounds, undoaction, 0), kw,
##                           self.vf.GetSubvolume.name)

    def switch_box(self):
        """ Shows/hides an outline of parent(main) volume """
        if self.checkvar.get() == 1:
            if not self.main_box:
                vx, vy, vz = self.parvol_size
                self.main_box = Box('mainvolbox', cornerPoints =
                                ((-vx/2., -vy/2., -vz/2.),
                                 (vx/2., vy/2., vz/2.)))
                self.vf.GUI.VIEWER.AddObject(self.main_box)
                self.update_box()
            self.main_box.visible = 1
        else:
           if not self.main_box:
               return
           self.main_box.visible = 0
        self.update_box()
        self.vf.GUI.VIEWER.deleteOpenglList()
        self.vf.GUI.VIEWER.Redraw()

    def change_boxsize(self):
        """Updates the size of the box otlining parent (original)
        volume"""
        
        vx, vy, vz = self.parvol_size
        self.main_box.Set(cornerPoints = ((-vx/2., -vy/2., -vz/2.),
                                 (vx/2., vy/2., vz/2.)))
        self.update_box()
        self.vf.GUI.VIEWER.Redraw()

    def update_box(self):
        """Sets correct scale and translation to the box outlining
        parent (original) volume """
        
        # Has to be implemented by a derived class
        pass

    def resetCrop_cb(self):
        """Resets the crop box to the volume size"""
        cropBox = volGeom.cropBox
        cropBox.xmin = 0
        cropBox.xmax = cropBox.volSize[0]
        cropBox.ymin = 0
        cropBox.ymax = cropBox.volSize[1]
        cropBox.zmin = 0
        cropBox.zmax = cropBox.volSize[2]
        cropBox.update()

class VLISubvolume(GetSubvolume):
    """Commnd to get a subvolume from a displayed VLI volume object and
    to load it in to the viewer as a separate volume. """

    def getDataArr(self, x,y,z):
        """Returns a Numeric Array of volume data. """
        from Volume.Renderers.VLI import vli
        vol = volGeom.volume
        self.voxformat = vol.GetFormat()
        volGeom.lock.acquire()
        #volGeom.render = 0
        #t1 = time()
        out = vol.MapVolume(2)
        ptr = out[1]
        if self.voxformat == vli.kVLIVoxelFormatUINT8:
            data = vli.createNumArr(ptr, z, y, x, vli.PyArray_UBYTE)
        else:
            data = vli.createNumArr(ptr, z, y, x, vli.PyArray_SHORT)
        status = vol.UnmapVolume()
        #volGeom.render = 1
        volGeom.lock.release()
        return data

    def createNewVolume(self, narr, dx, dy, dz):
        """Creates new volume object and adds it to the viewer.""" 
        from Volume.Renderers.VLI import vli
        arrPtr = vli.NumByteArrToVoidPt(narr)
        vlivol = vli.VLIVolume_Create( self.voxformat, arrPtr, dx, dy, dz)
        if not vlivol:
            print "WARNING: getsubvolCommands: volume was not created"
            return
        #print "volume created"
        volGeom.freePointer = 0
        volGeom.AddVolume(vlivol, arrPtr)
        volGeom.dataArr = narr

    def update_box(self):
        """Sets correct scale and translation to the box outlining
        parent (original) volume """
        
        xmin, xmax, ymin, ymax, zmin, zmax = self.bounds_list[-1]
        dx = xmax - xmin
        dy = ymax - ymin
        dz = zmax - zmin
        vx, vy, vz = self.parvol_size
        tx = (vx - dx)/2 - xmin
        ty = (vy - dy)/2 - ymin
        tz = (vz - dz)/2 - zmin
        scale = volGeom.vliScalemat #VLIMatrix instance
        trans = volGeom.vliTranslate #VLIMatrix instance
        sc = Numeric.identity(4).astype('f')
        if scale:
            sc[0][0] = scale[0][0]
            sc[1][1] = scale[1][1]
            sc[2][2] = scale[2][2]
            self.main_box.SetScale((scale[0][0],scale[1][1], scale[2][2]))
        if trans:
            tr = Numeric.identity(4).astype('f')
            tr[:,3] = [trans[0][3]+tx,trans[1][3]+ty, trans[2][3]+tz,1.0]
            tr = Numeric.dot(sc,tr)
            self.main_box.SetTranslation(tr[:3,3])
        else:
            self.main_box.SetTranslation(Numeric.array([tx,ty,tz],'f'))
        

        
commandGUI = CommandGUI()
menuName = 'Get Subvolume'
menuIndex = 12
commandNames = {'vli': 'VLISubvolume', 'utvolren': None}
commandList = [{'name': '', 'cmd': None, 'gui':commandGUI}]


def initModule(viewer):
    for dict in commandList:
        if dict['name']:
            viewer.addCommand(dict['cmd'], dict['name'], dict['gui'])
