# Copyright (c) 2014, Dignity Health
# 
#     The GPI core node library is licensed under
# either the BSD 3-clause or the LGPL v. 3.
# 
#     Under either license, the following additional term applies:
# 
#         NO CLINICAL USE.  THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL
# PURPOSES AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES.  THE
# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC
# PURPOSES.  YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR
# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT LIMITED
# TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES.  LICENSOR MAKES NO
# WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE SOFTWARE IN ANY
# HIGH RISK OR STRICT LIABILITY ACTIVITIES.
# 
#     If you elect to license the GPI core node library under the LGPL the
# following applies:
# 
#         This file is part of the GPI core node library.
# 
#         The GPI core node library is free software: you can redistribute it
# and/or modify it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version. GPI core node library is distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Lesser General Public License for more details.
# 
#         You should have received a copy of the GNU Lesser General Public
# License along with the GPI core node library. If not, see
# <http://www.gnu.org/licenses/>.


# Author: Jim Pipe
# Date: 2013Jun18

import copy
import gpi
import numpy as np
import gpi.GLObjects as glo

class ExternalNode(gpi.NodeAPI):
    """Reformats spin magnetization profile data generated by Spyn and Bloch
    nodes for visualization in an OpenGL viewer node.

    INPUT: MR Spin info from Bloch or Spyn module

    OUTPUTS:
    Spin Objects - graphical objects showing spins (send to GLViewer)
    PS Waveform Objects - graphical objects showing Gradient and RF waveforms (send to GLViewer)

    WIDGETS:
    Time Index - select the time point to display.  The duration per point is set by the Spyn module

    *** The following modules affect the Spin Objects List
    A/B/C Axis - select the variable to spread along the A/B/C axis
                (A/B/C are the x', y', z' axes in the GLViewer, color coded as R/G/B, respectively)
    A/B/C Axis stagger - select the distance between spins along the A/B/C axis
    Show - select a few options (can select multiple buttons)
      Axes - show the actual A/B/C axes (color coded as R/G/B)
      Text - label each axis with the variable displayed on that axis
      Mx=0 - display spin vectors without the Mx component
      My=0 - display spin vectors without the My component
      Mz=0 - display spin vectors without the Mz component
    Color Scheme - Select the color scheme for spin objects (spheres and cylinders)
      Off - white
      M3 - Mx/My/Mz fed into the R/G/B channel
      MG - color coded based on spin magnitude
      MT - color coded based on transverse magnetization
      PH - color coded basd on phase
      MTPH - same as PH, with brightness proportional to transverse magnetization
    Tip Connect - draw lines between the tips of spin vectors in the chosen direction
    Vector Radius - relative radius of cylinders used for spins
    Sphere Radius - relative radius of spheres at base of spin vectors
    Tracer History - enables drawing lines between locations of each spin-tips last n positions (sparkler effect)
    Vector History - enables drawing last n vectors

    *** The following modules affect the PS Waveform Objects List
    Waveforms - Select which waveforms to view
    Waveform Stretch - a kludgy way to adjust the aspect ratio of the PS Waveform plot (affects X-axis spacing)
    """
    ###### FOR REFERENCE ############
    # 0  1  2  3  4  5  6  7  8  9  10 11 12
    # M0 T1 T2 Vx Vy Vz Fq Dx Dy Dz dt __ __
    # Mx My Mz X  Y  Z  T  Gx Gy Gz Rx Ry Sp
    #################################

    def initUI(self):
        # Widgets
        self.addWidget('Slider', 'Time index')
        self.addWidget('ExclusivePushButtons', 'A Axis',
                       buttons=['T1','T2','FQ','X','Y','Z','Vx','Vy','Vz'], val=0)
        self.addWidget('DoubleSpinBox', 'A Axis stagger',min=0.,max=2.,val=0.,singlestep = 0.1,immediate=True)
        self.addWidget('ExclusivePushButtons', 'B Axis',
                       buttons=['T1','T2','FQ','X','Y','Z','Vx','Vy','Vz'], val=0)
        self.addWidget('DoubleSpinBox', 'B Axis stagger',min=0.,max=2.,val=0.,singlestep = 0.1,immediate=True)
        self.addWidget('ExclusivePushButtons', 'C Axis',
                       buttons=['T1','T2','FQ','X','Y','Z','Vx','Vy','Vz'], val=0)
        self.addWidget('DoubleSpinBox', 'C Axis stagger',min=0.,max=2.,val=0.,singlestep = 0.1,immediate=True)
        self.addWidget('Slider', 'T1 index')
        self.addWidget('Slider', 'T2 index')
        self.addWidget('Slider', 'FQ index')
        self.addWidget('Slider', 'X index')
        self.addWidget('Slider', 'Y index')
        self.addWidget('Slider', 'Z index')
        self.addWidget('Slider', 'Vx index')
        self.addWidget('Slider', 'Vy index')
        self.addWidget('Slider', 'Vz index')

        self.addWidget('NonExclusivePushButtons', 'Show',
                       buttons=['Axes','Text','Mx=0','My=0','Mz=0'],val=0)
        self.addWidget('ExclusivePushButtons', 'Color Scheme',
                       buttons=['Off','M3','MG','MT','PH','MTPH'], val=0)
        self.addWidget('ExclusivePushButtons', 'Tip Connect',
                       buttons=['OFF','T1','T2','FQ','X','Y','Z','Vx','Vy','Vz'], val=0)
        self.addWidget('Slider', 'Tip Connector Radius',val=4,max=10)
        self.addWidget('Slider', 'Vector Radius',val=4,max=10)
        self.addWidget('Slider', 'Sphere Radius')
        self.addWidget('Slider', 'Tracer History')
        self.addWidget('Slider', 'Vector History')
        self.addWidget('NonExclusivePushButtons', 'Waveforms',
                       buttons=['GX','GY','GZ','RF_r','RF_i','|RF|','RF_ph'],val=0)
        self.addWidget('Slider', 'Waveform Stretch',min=1,max=100,val=20)

        # IO Ports
        self.addInPort('M_in', 'NPYarray')
        self.addOutPort('Spin Objects', 'GLOList')
        self.addOutPort('PS Waveform Objects', 'GLOList')

        return 0

    def validate(self):
        m_in = self.getData('M_in')
        mdim = list(m_in.shape)
        options = ['Off','T1','T2','FQ','X','Y','Z','Vx','Vy','Vz']
        tipOptions = ['Off']
        aAxis = self.getVal('A Axis')
        bAxis = self.getVal('B Axis')
        cAxis = self.getVal('C Axis')

        # Set limits on index sliders
        self.setAttr('Time index',max=mdim[1]-2)
        self.setAttr('T1 index',max=mdim[2]-1)
        self.setAttr('T2 index',max=mdim[3]-1)
        self.setAttr('FQ index',max=mdim[4]-1)
        self.setAttr( 'X index',max=mdim[5]-1)
        self.setAttr( 'Y index',max=mdim[6]-1)
        self.setAttr( 'Z index',max=mdim[7]-1)
        self.setAttr('Vx index',max=mdim[8]-1)
        self.setAttr('Vy index',max=mdim[9]-1)
        self.setAttr('Vz index',max=mdim[10]-1)

        if self.getVal('Tip Connect') == 0:
          self.setAttr('Tip Connector Radius',visible = False)
        else:
          self.setAttr('Tip Connector Radius',visible = True)

        # A Axis, show options with more than 1 element
        aOptions = ['Off']
        for i in range(9):
          if mdim[i+2] > 1:
            aOptions.append(options[i+1])
        if len(aOptions) > 1:
          self.setAttr('A Axis',buttons=aOptions)
        if self.getVal('A Axis') >= len(aOptions):
          self.setAttr('A Axis',val=0)
        self.setAttr('A Axis',visible=len(aOptions) > 1)
        self.setAttr('A Axis stagger',visible=(len(aOptions) > 1) and (self.getVal('A Axis') > 0))
        if self.getVal('A Axis') > 0:
          tipOptions.append(aOptions[self.getVal('A Axis')])
         
        # B Axis, show remaining viable options
        bOptions = aOptions
        if len(bOptions) > 1:
          if self.getVal('A Axis') is not None:
            if self.getVal('A Axis') > 0: # Preserve the "Off" option
              bOptions.remove(aOptions[self.getVal('A Axis')])
        if len(bOptions) > 1:
          self.setAttr('B Axis',buttons=bOptions)
        if self.getVal('B Axis') >= len(bOptions):
          self.setAttr('B Axis',val=0)
        self.setAttr('B Axis',visible=len(bOptions) > 1)
        self.setAttr('B Axis stagger',visible=(len(bOptions) > 1) and (self.getVal('B Axis') > 0))
        if self.getVal('B Axis') > 0:
          tipOptions.append(bOptions[self.getVal('B Axis')])

        # C Axis, show remaining viable options
        cOptions = bOptions
        if len(cOptions) > 1:
          if self.getVal('B Axis') is not None:
            if self.getVal('B Axis') > 0: # Preserve the "Off" option
              cOptions.remove(bOptions[self.getVal('B Axis')])
        if len(cOptions) > 1:
          self.setAttr('C Axis',buttons=cOptions)
        if self.getVal('C Axis') >= len(cOptions):
          self.setAttr('C Axis',val=0)
        self.setAttr('C Axis',visible=len(cOptions) > 1)
        self.setAttr('C Axis stagger',visible=(len(cOptions) > 1) and (self.getVal('C Axis') > 0))
        if self.getVal('C Axis') > 0:
          tipOptions.append(cOptions[self.getVal('C Axis')])

        self.setAttr('Tip Connect',buttons=tipOptions)

        # Now Show remaining index sliders
        options.remove('Off')
        if len(cOptions) > 1:
          cOptions.remove(cOptions[self.getVal('C Axis')])
        for stuff in options:
          if cOptions.count(stuff) == 0:
            self.setAttr(stuff+' index',visible=False)
          else:
            self.setAttr(stuff+' index',visible=True)

        gwaveList = self.getVal('Waveforms')
        if gwaveList:
          self.setAttr('Waveform Stretch',visible = True)
        else:
          self.setAttr('Waveform Stretch',visible = False)

        return 0

    ############################
    def compute(self):
    ############################
    ###### FOR REFERENCE ############
    # 0  1  2  3  4  5  6  7  8  9  10 11 12
    # M0 T1 T2 Vx Vy Vz Fq Dx Dy Dz dt __ __
    # Mx My Mz X  Y  Z  T  Gx Gy Gz Rx Ry Sp
    #################################
        import numpy as np
        import math

        # In this module we will take m_in and pare it down
        # We will first get rid of all indices of length 1
        # Then we will slice through all dimensions that get sliced
        # and reorder the dimensions to display according to the A, B, and C axes
        # (note we will talk about A, B, C axes in the geometry viewer so we don't
        #  get confused with the X, Y, Z dimensions specified in the Spyn module)

        timeIndex = self.getVal('Time index')
        m_in = self.getData('M_in')
        mdim = list(m_in.shape)
        totalOptions = ['T1','T2','FQ','X','Y','Z','Vx','Vy','Vz']
        viableOptions = list(totalOptions)

        # Calculate the distance between points in the X/Y/Z dimensions for later use
        # This will be used to display spins in the X/Y/Z dimensions with velocity
        # Again, this is the physical X/Y/Z specified in Spyn, NOT the axes of display
        # off-xyz-scale is in units of "spin separation units per m"
        if m_in[3,1,0,0,0,0,0,0,0,0,0] != m_in[3,1,0,0,0,-1,0,0,0,0,0]:
          offxscale = (m_in.shape[5]-1)/(m_in[3,1,0,0,0,-1,0,0,0,0,0] - m_in[3,1,0,0,0,0,0,0,0,0,0])
        else:
          offxscale = 0
        if m_in[4,1,0,0,0,0,0,0,0,0,0] != m_in[4,1,0,0,0,0,-1,0,0,0,0]:
          offyscale = (m_in.shape[6]-1)/(m_in[4,1,0,0,0,0,-1,0,0,0,0] - m_in[4,1,0,0,0,0,0,0,0,0,0])
        else:
          offyscale = 0
        if m_in[5,1,0,0,0,0,0,0,0,0,0] != m_in[5,1,0,0,0,0,0,-1,0,0,0]:
          offzscale = (m_in.shape[7]-1)/(m_in[5,1,0,0,0,0,0,-1,0,0,0] - m_in[5,1,0,0,0,0,0,0,0,0,0])
        else:
          offzscale = 0
        velx = m_in[3,0,0,0,0,0,0,0,0,0,0]
        vely = m_in[4,0,0,0,0,0,0,0,0,0,0]
        velz = m_in[5,0,0,0,0,0,0,0,0,0,0]
        Tstart = m_in[6,1,0,0,0,0,0,0,0,0,0]
        xref  = m_in[3,1,0,0,0,:,0,0,0,0,0]-velx*Tstart # X position array at T=0
        yref  = m_in[4,1,0,0,0,0,:,0,0,0,0]-vely*Tstart # Y position array at T=0
        zref  = m_in[5,1,0,0,0,0,0,:,0,0,0]-velz*Tstart # Z position array at T=0

        # Remove all indices of length 1
        for i in reversed(list(range(9))):
          if mdim[i+2] == 1:
            viableOptions.pop(i)
        m_in = np.squeeze(m_in)

        # Identify axes
        aAxis = self.getVal('A Axis')
        bAxis = self.getVal('B Axis')
        cAxis = self.getVal('C Axis')
        aAxisText = "off"
        bAxisText = "off"
        cAxisText = "off"
        remainingOptions = list(viableOptions)
        axesChosen = []
        if aAxis > 0:
          axesChosen.append(remainingOptions[aAxis-1])
          aAxisText = remainingOptions[aAxis-1]
          remainingOptions.pop(aAxis-1)
        else:
          axesChosen.append('Off')
        if bAxis > 0:
          axesChosen.append(remainingOptions[bAxis-1])
          bAxisText = remainingOptions[bAxis-1]
          remainingOptions.pop(bAxis-1)
        else:
          axesChosen.append('Off')
        if cAxis > 0:
          axesChosen.append(remainingOptions[cAxis-1])
          cAxisText = remainingOptions[cAxis-1]
          remainingOptions.pop(cAxis-1)
        else:
          axesChosen.append('Off')

        # Now run through axes yet to slice
        # remaining options lists what is yet to be accounted for
        # sliceOptions gives the order of what's left in m_in, other than the 1st 2 dimensions (13,Time)
        tHist = self.getVal('Tracer History')
        vHist = self.getVal('Vector History')
        dim1wid = min(timeIndex,max(tHist, vHist))
        xi = []
        Ax = []
        xi.append(slice(None)) # Mx My Mz
        xi.append(slice(1+timeIndex-dim1wid,2+timeIndex)) # Time
        for i in range(len(viableOptions)):
          if viableOptions[i] in remainingOptions:
            sl = self.getVal(viableOptions[i]+' index')
            xi.append(sl)
          else:
            xi.append(slice(None))
            Ax.append(axesChosen.index(viableOptions[i]))
        m_in = m_in[xi]

        # OK, now m_in has between 2 and 5 dimensions
        # The first dimension has 13 indices, which includes Mx, My, and Mz
        # The second dimension has time points, just enough to do tracer and vector history
        # The next (up to) 3 dimensions are the axes along with the spins will be displayed
        # the Ax list gives which Axis the corresponding dimension belongs to
        # For example, Ax = [2,0], where m_in is 4 dimensions, means that spins are displayed 
        #             along the C axis first, the the A axis

        # Put A axis first`(in 3rd dim of m_in)
        if Ax.count(0) > 0:
          ii = Ax.index(0)
          if ii > 0:
            Ax[ii] = Ax[0]
            m_in = m_in.swapaxes(ii+2,2)
        else:
          m_in = np.expand_dims(m_in,2)
          Ax.insert(0,0)

        # Put B axis next`(in 4th dim of m_in)
        if Ax.count(1) > 0:
          ii = Ax.index(1)
          if ii > 1:
            m_in = m_in.swapaxes(ii+2,3)
        else:
          m_in = np.expand_dims(m_in,3)
          Ax.insert(1,1)

        # Put C axis last`(in 5th dim of m_in)
        # Note it should be there already, unless len(Ax) < 3
        if len(Ax) != 3:
          m_in = np.expand_dims(m_in,4)

        # Tip Connecting Logic
        tipRad = 0.01*self.getVal('Tip Connector Radius')
        tipCon = self.getVal('Tip Connect')
        if tipCon > 0 and aAxis == 0:
          tipCon += 1
        if tipCon > 1 and bAxis == 0:
          tipCon += 1

        itip = jtip = ktip = 0
        if tipCon == 1:
          itip = 1
        if tipCon == 2:
          jtip = 1
        if tipCon == 3:
          ktip = 1
        
        astag = self.getVal('A Axis stagger')
        bstag = self.getVal('B Axis stagger')
        cstag = self.getVal('C Axis stagger')
        sphereRad = 0.01*self.getVal('Sphere Radius')
        vectorRad = 0.01*self.getVal('Vector Radius')
        showList = self.getVal('Show')
        showAxes = (np.array(showList) == 0).any()
        showText = (np.array(showList) == 1).any()
        showMx   = not (np.array(showList) == 2).any()
        showMy   = not (np.array(showList) == 3).any()
        showMz   = not (np.array(showList) == 4).any()
        colorscheme = self.getVal('Color Scheme')
        amax = m_in.shape[2]
        bmax = m_in.shape[3]
        cmax = m_in.shape[4]
        aPos0 = -(amax-1)*astag*0.5
        bPos0 = -(bmax-1)*bstag*0.5
        cPos0 = -(cmax-1)*cstag*0.5
        aPos1 =  (amax-1)*astag*0.5
        bPos1 =  (bmax-1)*bstag*0.5
        cPos1 =  (cmax-1)*cstag*0.5

        #out = []
        out = glo.ObjectList()

        # Axes
        if showAxes:
          if (amax > 1):
            RGBA = (1.,0.,0.,1.)
            desc = {}
            desc = glo.Cylinder()
            desc.setSubdiv(10)
            desc.setBase(vectorRad)
            desc.setTop(vectorRad)
            desc.setRGBA(RGBA)
            desc.setP1P2((aPos0,0,0), (aPos1,0,0))
            out.append(desc)
       
          if (bmax > 1):
            RGBA = (0.,1.,0.,1.)
            desc = {}
            desc = glo.Cylinder()
            desc.setSubdiv(10)
            desc.setBase(vectorRad)
            desc.setTop(vectorRad)
            desc.setRGBA(RGBA)
            desc.setP1P2((0,bPos0,0), (0,bPos1,0))
            out.append(desc)

          if (cmax > 1):
            RGBA = (0.,0.,1.,1.)
            desc = {}
            desc = glo.Cylinder()
            desc.setSubdiv(10)
            desc.setBase(vectorRad)
            desc.setTop(vectorRad)
            desc.setRGBA(RGBA)
            desc.setP1P2((0,0,cPos0), (0,0,cPos1))
            out.append(desc)

        # Text
        if showText:
          if (amax > 1):
            RGBA = (1.,0.,0.,1.)
            desc = {}
            desc = glo.Text()
            desc.setRGBA(RGBA)
            desc.setPos((aPos1, 0, 0))
            desc.setText(aAxisText)
            out.append(desc)
          if (bmax > 1):
            RGBA = (0.,1.,0.,1.)
            desc = {}
            desc = glo.Text()
            desc.setRGBA(RGBA)
            desc.setPos((0,bPos1, 0))
            desc.setText(bAxisText)
            out.append(desc)
          if (cmax > 1):
            RGBA = (0.,0.,1.,1.)
            desc = {}
            desc = glo.Text()
            desc.setRGBA(RGBA)
            desc.setPos((0,0,cPos1))
            desc.setText(cAxisText)
            out.append(desc)

###### FOR REFERENCE ############
# 0  1  2  3  4  5  6  7  8  9  10 11 12
# M0 T1 T2 Vx Vy Vz Fq Dx Dy Dz dt __ __
# Mx My Mz X  Y  Z  T  Gx Gy Gz Rx Ry Sp
#################################
          
        # Now Loop through Spins
        for k in range(cmax):
          cPosbase = cPos0+k*cstag
          for j in range(bmax):
            bPosbase = bPos0+j*bstag
            for i in range(amax):
              aPosbase = aPos0+i*astag
              mx = m_in[0,dim1wid,i,j,k]
              my = m_in[1,dim1wid,i,j,k]
              mz = m_in[2,dim1wid,i,j,k]

              # This is some logic for shifting X, Y, and Z for velocities != 0
              if aAxisText == 'X':
                aPos = aPosbase + astag*(m_in[3,-1,i,j,k]-xref[i])*offxscale
              elif aAxisText == 'Y':
                aPos = aPosbase + astag*(m_in[4,-1,i,j,k]-yref[i])*offyscale
              elif aAxisText == 'Z':
                aPos = aPosbase + astag*(m_in[5,-1,i,j,k]-zref[i])*offzscale
              else:
                aPos = aPosbase
              if bAxisText == 'X':
                bPos = bPosbase + bstag*(m_in[3,-1,i,j,k]-xref[j])*offxscale
              elif bAxisText == 'Y':
                bPos = bPosbase + bstag*(m_in[4,-1,i,j,k]-yref[j])*offyscale
              elif bAxisText == 'Z':
                bPos = bPosbase + bstag*(m_in[5,-1,i,j,k]-zref[j])*offzscale
              else:
                bPos = bPosbase
              if cAxisText == 'X':
                cPos = cPosbase + cstag*(m_in[3,-1,i,j,k]-xref[k])*offxscale
              elif cAxisText == 'Y':
                cPos = cPosbase + cstag*(m_in[4,-1,i,j,k]-yref[k])*offyscale
              elif cAxisText == 'Z':
                cPos = cPosbase + cstag*(m_in[5,-1,i,j,k]-zref[k])*offzscale
              else:
                cPos = cPosbase

              # COLOR SCHEME
              # ['Off','M3','MG','MT','PH']
              brightness = 1.
              if colorscheme == 0: # Off
                red = green = blue = 1.
                clookup = 'none'
              elif colorscheme == 1: # M3
                red = abs(mx)
                green = abs(my)
                blue = abs(mz)
                clookup = 'none'
              elif colorscheme == 2: # MG
                color = 5*np.sqrt(mx*mx + my*my + mz*mz)
                clookup = 'rgb'
              elif colorscheme == 3: # MT
                color = 5*np.sqrt(mx*mx + my*my)
                clookup = 'rgb'
              elif colorscheme == 4: # PH
                color = 3.*(np.pi+np.arctan2(mx,my))/np.pi
                clookup = 'rgb'
              elif colorscheme == 5: # MT-PH
                color = 3.*(np.pi+np.arctan2(mx,my))/np.pi
                brightness = np.sqrt(mx*mx + my*my)
                clookup = 'rgb'
               
              if clookup == 'rgb':
                if color < 1:
                  red = 1.
                  green = color
                  blue = 0
                elif color < 2:
                  red = 2-color
                  green = 1.
                  blue = 0
                elif color < 3:
                  red = 0
                  green = 1.
                  blue = color-2
                elif color < 4:
                  red = 0
                  green = 4-color
                  blue = 1
                elif color < 5:
                  red = color-4
                  green = 0
                  blue = 1
                elif color <= 6:
                  red = 1.
                  green = 0
                  blue = 6-color

              red   *= brightness
              green *= brightness
              blue  *= brightness
              RGBA = (red,green,blue,1.)

              mx *= showMx
              my *= showMy
              mz *= showMz

              # VECTORS
              if vectorRad > 0.:
                desc = {}
                desc = glo.Cylinder()
                desc.setRGBA(RGBA)
                desc.setSubdiv(10)
                desc.setBase(vectorRad)
                desc.setTop(vectorRad)
                desc.setP1P2((aPos,bPos,cPos), (aPos+mx,bPos+my,cPos+mz))
                out.append(desc)

                # Vector History
                for hist in range(min(dim1wid,vHist)):
                  mx0 = m_in[0,dim1wid-1-hist,i,j,k]
                  my0 = m_in[1,dim1wid-1-hist,i,j,k]
                  mz0 = m_in[2,dim1wid-1-hist,i,j,k]
                  histRad = vectorRad*(vHist-hist)/(vHist+1.)
                  desc = {}
                  desc = glo.Cylinder()
                  desc.setRGBA(RGBA)
                  desc.setSubdiv(10)
                  desc.setBase(histRad)
                  desc.setTop(histRad)
                  desc.setP1P2((aPos,bPos,cPos), (aPos+mx0,bPos+my0,cPos+mz0))
                  out.append(desc)

                # Tracer History
                for hist in range(min(dim1wid,tHist)):
                  mx0 = m_in[0,dim1wid-hist,i,j,k]
                  my0 = m_in[1,dim1wid-hist,i,j,k]
                  mz0 = m_in[2,dim1wid-hist,i,j,k]
                  mx1 = m_in[0,dim1wid-1-hist,i,j,k]
                  my1 = m_in[1,dim1wid-1-hist,i,j,k]
                  mz1 = m_in[2,dim1wid-1-hist,i,j,k]
                  tRad0 = vectorRad*(tHist-hist)/(tHist)
                  tRad1 = vectorRad*(tHist-hist-1)/(tHist)
                  desc = {}
                  desc = glo.Cylinder()
                  desc.setRGBA(RGBA)
                  desc.setSubdiv(10)
                  desc.setBase(tRad1)
                  desc.setTop(tRad0)
                  desc.setP1P2((aPos+mx0,bPos+my0,cPos+mz0), (aPos+mx1,bPos+my1,cPos+mz1))
                  out.append(desc)

              # TIP CONNECT
              if tipCon > 0:
                if i>=itip and j>=jtip and k>=ktip:
                  mx0 = m_in[0,dim1wid,i-itip,j-jtip,k-ktip]
                  my0 = m_in[1,dim1wid,i-itip,j-jtip,k-ktip]
                  mz0 = m_in[2,dim1wid,i-itip,j-jtip,k-ktip]
                  desc = {}
                  desc = glo.Cylinder()
                  desc.setRGBA(RGBA)
                  desc.setSubdiv(10)
                  desc.setBase(tipRad)
                  desc.setTop(tipRad)
                  desc.setP1P2((aPos-itip*astag+mx0,bPos-jtip*bstag+my0,cPos-ktip*cstag+mz0), (aPos+mx,bPos+my,cPos+mz))
                  out.append(desc)

              # VECTORS
              if sphereRad > 0.:
                desc = {}
                desc = glo.Sphere()
                desc.setRadius(sphereRad)
                desc.setRGBA(RGBA)
                desc.setPos((aPos,bPos,cPos))
                desc.setSubdiv(20)
                out.append(desc)

        # Gradient Waveforms
###### FOR REFERENCE ############
# 0  1  2  3  4  5  6  7  8  9  10 11 12
# M0 T1 T2 Vx Vy Vz Fq Dx Dy Dz dt __ __
# Mx My Mz X  Y  Z  T  Gx Gy Gz Rx Ry Sp
#################################
        psw = glo.ObjectList()
        gwaveList = self.getVal('Waveforms')
        if gwaveList:
          wavex = 0.005*self.getVal('Waveform Stretch')
          showGx = (np.array(gwaveList)  == 0).any()
          showGy = (np.array(gwaveList)  == 1).any()
          showGz = (np.array(gwaveList)  == 2).any()
          showRfr = (np.array(gwaveList) == 3).any()
          showRfi = (np.array(gwaveList) == 4).any()
          showRfm = (np.array(gwaveList) == 5).any()
          showRfp = (np.array(gwaveList) == 6).any()
          numwaves = int(showGx) + int(showGy) + int(showGz) + int(showRfr) + int(showRfi) + int(showRfm) + int(showRfp)

          g_in = self.getData('M_in')[7:,1:,0,0,0,0,0,0,0,0,0]
          maxvals = np.amax(np.absolute(g_in), axis=1)
          gmax = np.amax(maxvals[0:3])
          rfmax = math.sqrt(maxvals[3]*maxvals[3] + maxvals[4]*maxvals[4])
          if gmax > 0:
            gnorm = 1./gmax
          else:
            gnorm = 0
          if rfmax > 0:
            rfnorm = 1./rfmax
          else:
            rfnorm = 0

          # Draw Axes
          RGBA = (1.0,1.0,1.0,1)
          x0 = 0
          x1 = g_in.shape[1] * wavex
          for i in range(numwaves):
            y = 2.5*(numwaves-1-2*i)/2
            desc = {}
            desc = glo.Cylinder()
            desc.setRGBA(RGBA)
            desc.setSubdiv(10)
            desc.setBase(0.05)
            desc.setTop(0.05)
            desc.setP1P2((x0,y,0), (x1,y,0))
            psw.append(desc)

          for i in range(timeIndex+1):
            x0 = i*wavex
            x1 = x0 - wavex
            y = 2.5*(numwaves-1)/2
            ii = max(i-1,0)
            if showGx:
              y0 = y+gnorm*g_in[0,i]
              y1 = y+gnorm*g_in[0,ii]
              RGBA = (1.0,0.5,0.5,1)
              desc = {}
              desc = glo.Cylinder()
              desc.setRGBA(RGBA)
              desc.setSubdiv(10)
              desc.setBase(0.05)
              desc.setTop(0.05)
              desc.setP1P2((x0,y0,0), (x1,y1,0))
              psw.append(desc)
              y -= 2.5
            if showGy:
              y0 = y+gnorm*g_in[1,i]
              y1 = y+gnorm*g_in[1,ii]
              RGBA = (0.5,1.0,0.5,1)
              desc = {}
              desc = glo.Cylinder()
              desc.setRGBA(RGBA)
              desc.setSubdiv(10)
              desc.setBase(0.05)
              desc.setTop(0.05)
              desc.setP1P2((x0,y0,0), (x1,y1,0))
              psw.append(desc)
              y -= 2.5
            if showGz:
              y0 = y+gnorm*g_in[2,i]
              y1 = y+gnorm*g_in[2,ii]
              RGBA = (0.5,0.5,1.0,1)
              desc = {}
              desc = glo.Cylinder()
              desc.setRGBA(RGBA)
              desc.setSubdiv(10)
              desc.setBase(0.05)
              desc.setTop(0.05)
              desc.setP1P2((x0,y0,0), (x1,y1,0))
              psw.append(desc)
              y -= 2.5
            if showRfr:
              y0 = y+rfnorm*g_in[3,i]
              y1 = y+rfnorm*g_in[3,ii]
              RGBA = (1.0,1.0,0.5,1)
              desc = {}
              desc = glo.Cylinder()
              desc.setRGBA(RGBA)
              desc.setSubdiv(10)
              desc.setBase(0.05)
              desc.setTop(0.05)
              desc.setP1P2((x0,y0,0), (x1,y1,0))
              psw.append(desc)
              y -= 2.5
            if showRfi:
              y0 = y+rfnorm*g_in[4,i]
              y1 = y+rfnorm*g_in[4,ii]
              RGBA = (1.0,0.5,1.0,1)
              desc = {}
              desc = glo.Cylinder()
              desc.setRGBA(RGBA)
              desc.setSubdiv(10)
              desc.setBase(0.05)
              desc.setTop(0.05)
              desc.setP1P2((x0,y0,0), (x1,y1,0))
              psw.append(desc)
              y -= 2.5

        if out.len() == 0: out = None
        if psw.len() == 0: psw = None
        self.setData('Spin Objects', out)
        self.setData('PS Waveform Objects', psw)

        return 0

    def execType(self):
        '''Could be GPI_THREAD, GPI_PROCESS, GPI_APPLOOP'''
        return gpi.GPI_PROCESS
