"""
    @file  utilities.py
    @brief Handy utility functions for NIfTI data.

    @note This file originally has been copied from the trunk of the
          sbiaUtilities project revision 297 on 4/24/2012.
          The execCmd() function was updated on 5/21/2012 to revision
          304 of the sbiaUtilities version of this file.

    Copyright (c) 2008, 2009, 2012, 2013 University of Pennsylvania.

    This file is part of DTI-DROID.

    DTI-DROID is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DTI-DROID 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DTI-DROID.  If not, see <http://www.gnu.org/licenses/>.
"""

import os, sys, traceback
import subprocess, re, shutil, tempfile
from numpy import array, ndarray, floating, zeros
from nifti import NiftiImage
try:
  from nifti import clib
except:
  from nifti import nifticlib
from types import ListType

###########################################################################
##########    EXEC FUNCTIONS    ###########################################
###########################################################################

class SbiaException(Exception):
  """our exception class. You can raise it with a msg"""
#  def _get_message(self, message): return self._message
#  def _set_message(self, message): self._message = message
#  message = property(_get_message, _set_message)
  
  def __init__(self, msg):
    self.message = msg 
  def __str__(self):
    return repr(self.message)

def detectCPUs():
  """
  Detects the number of CPUs on a system. Cribbed from pp.
  taken from Both
  http://codeliberates.blogspot.com/2008/05/detecting-cpuscores-in-python.html
  http://www.artima.com/weblogs/viewpost.jsp?thread=230001
  """
  # Linux, Unix and MacOS:
  if hasattr(os, "sysconf"):
    if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
      # Linux & Unix:
      ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
      if isinstance(ncpus, int) and ncpus > 0:
        return ncpus
    else: # OSX:
      return int(os.popen2("sysctl -n hw.ncpu")[1].read())

  # Windows:
  if os.environ.has_key("NUMBER_OF_PROCESSORS"):
    ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]);
    if ncpus > 0:
      return ncpus
  
  return 1 # Default

def execCmd(cmdArray,verbose=0,simulate=False,quiet=False,noFail=False,shell=False):
  """
  executes a command array by space-concatenating it and passing it to subprocess.Popen
  returns a tuple containing (return value, stdout, stderr)
  if the exit status is not 0, raises an SbiaException
    cmdArray - the input command array
    verbose  - turn on extra output
    simulate - simulate the run (no actual commands run)
    quiet    - turns off output of stdout and stderr to the screen
    noFail   - if true does not raise an exception if return value is not 0
    shell    - command can be a bash command
  """
  MAX_CMD_ARR_LEN = 20 # max number of items to dump
  
  cmdArray = [str(i) for i in cmdArray]
  if len(cmdArray) > MAX_CMD_ARR_LEN and verbose<2:
    cmd = " ".join(cmdArray[:MAX_CMD_ARR_LEN]) + " ...[" + str(len(cmdArray)) + "]"
  else:
    cmd = " ".join(cmdArray)
  
  # if verbose, print command which will be run
  if verbose or simulate:
    print '>>>', cmd
  
  # execute command
  ret = [0, '', '']
  if simulate:
    ret = [0, 'Simulated', 'Simulated']
  else:
    try:
      # open subprocess
      if shell:
        process = subprocess.Popen(' '.join(cmdArray), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable='/bin/bash')
      else:
        process = subprocess.Popen(cmdArray, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      # read stdout until EOF
      for line in process.stdout:
        ret[1] = ret[1] + line
        if not quiet and verbose:
          print line.rstrip()
          sys.stdout.flush()
      # wait until subprocess terminated and set exit code
      (out, err) = process.communicate()
      ret[1] = ret[1] + out
      ret[2] = err
      # get exit code
      ret[0] = process.returncode
      # print stdout on error if not done yet
      if not quiet and not verbose and ret[0] != 0:
        for line in ret[1]: sys.stdout.write(line)
        sys.stdout.flush()
      # print error messages of subprocess
      if not quiet and (verbose or ret[0] != 0):
        for line in ret[2]: sys.stderr.write(line)
    except OSError,e:
      msg = cmd + "\tCaused an OSError\n"
      msg = msg + "\t"+e.message
      sys.stderr.write(msg)
      raise e
    except Exception,e:
      sys.stderr.write(e.message)
      raise e

  if verbose>0 or simulate:
    print "return value: %d" % ret[0]

  # if this failed then throw an exception
  if ret[0] != 0 and not noFail:
    msg = cmd + " Failed!"
    raise SbiaException(msg)

  return ret

def checkCmdExist(executable):
  """
  checks whether the system can find an executable using 'which'
  if found, returns the path to this executable
    executable - the executable to check
  """  
  check = execCmd(["which",executable],quiet=True,noFail=True)
  if check[0] != 0:
    msg = "Error can't find " + executable + " please check your path!"
    raise SbiaException(msg)
  return getFilePath(check[1])

def printStackTrace():
  """prints an exception trace. To be used in an Except block"""
  traceback.print_exc(file=sys.stdout)
  
def cryandexit(error, filename=None, line=None, exit=True):
  """
  raises an error and exits
    error    - the error text
    filename - the thing the error is associated with
    line     - the line the error was encountered on
    exit     - True if you want this to raise a sys.exit
  """
  if (line == None and filename == None):
    msg='*** Error: %s!' % (error)
  elif (line == None):
    msg='*** Error in %s: %s!' % (str(filename), error)
  else:
    msg='*** Error in %s, line %s: %s!' % (str(filename), str(line), error)
  if exit:
    sys.exit(msg)
  else:
    print msg

###########################################################################
##########   FILE FUNCTIONS   #############################################
###########################################################################

def fileExists(fileName,isDir=False):
  """
  checks whether a file or dir exists
    filename - path to file
    isDir    - True if you want to check whether it is a directory
  """
  if isDir:
    return(os.path.isdir(fileName))
  else:
    return(os.path.isfile(fileName))

def isWritable(fileName):
  """
  checks whether directory is writable
  """
  return os.access(fileName,os.W_OK)

def makeDir(fileName,mode=0775):
  """
  recursively make all dirs required in a path
    filename - the paths
    mode     - permissions (default is 0755)
  """
  if fileExists(fileName,isDir=True):
    return 0
  elif os.path.exists(fileName):
    return -1
  else:
    os.makedirs(fileName,mode)
    return 0

def compressPaths(pathList, unique=True):
  """
  returns a globable path if the input filenames share a common and unique path
  else returns None. Used to replace a long list of filenames with /common/path/file*
  """
  if len(pathList) == 0:
    return None
  elif len(pathList) == 1:
    return pathList[0]
  common = commonName(pathList)
  if os.path.isdir(common):
    compress = os.path.join(os.path.realpath(common),"*")
  else:
    compress = os.path.realpath(common)+"*"
  if unique:
    from glob import glob
    list = [os.path.realpath(l) for l in glob(compress)]
    list.sort()
    pathList = [os.path.realpath(l) for l in pathList]
    pathList.sort()
    if pathList != list:
      return None
  return compress

def commonName(nameList):
  """returns the part of the filename common to all input files"""
  if len(nameList) == 0:
    raise SbiaException("This function requires at least 1 list item")
  if len(nameList) == 1:
    return nameList[0]
  common = nameList[0]
  for name in nameList[1:]:
    j = 0
    while j < min(len(common),len(name)) and common[j] == name[j]:
      j += 1
    if j < len(common):
      common = common[:j]
  return common

def getFileBase(fileName):
  """returns the name part of fileName"""
  root,ext = os.path.splitext(os.path.basename(fileName))
  if ext == '.gz':
    root2,ext2 = os.path.splitext(root)
    if ext2 == '.nii':
      return root2
  return root

def getFileExt(fileName):
  """returns the .extension part of fileName"""
  root,ext = os.path.splitext(os.path.basename(fileName))
  if ext == '.gz':
    root2,ext2 = os.path.splitext(root)
    if ext2 == '.nii':
      return ext2+ext
  return ext
  
def getFilePath(fileName):
  """returns the path to fileName"""
  return os.path.dirname(fileName)

def isNiftiFile(fileName):
  """returns True if fileName is a nifti file"""
  try:
    return clib.is_nifti_file(fileName) > 0
  except:
    return nifticlib.is_nifti_file(fileName) > 0

def isTensor(img):
  """returns true id image is a tensor"""
  if not isinstance(img, NiftiImage):
    img = NiftiImage(img)
  return img.raw_nimg.intent_code == 1005
  
def switchExt(filename, ext=None):
  """takes in name.hdr and returns name.img and the reverse"""
  if not ext:
    ext = os.path.splitext(filename)[1]
    if ext == ".img":
      ext = ".hdr"
    elif ext == ".hdr":
      ext = ".img"
    elif ext == ".BRIK":
      ext = ".HEAD"
    elif ext == ".HEAD":
      ext = ".BRIK"
    else:
      raise SbiaException("Can't switch extension '" + ext + "'")
  elif ext[0] != '.':
    ext = '.'+ext
  return os.path.splitext(filename)[0]+ext

def rmNifti(filename):
  """deletes a nifti file (both img and hdr when applicable)"""
  os.remove(filename)
  try:
    paired=switchExt(filename)
    os.remove(paired)
  except SbiaException:
    pass

def copyNifti(source, destination, move=False):
  """copies a Nifti or Afni volume to destination file or folder"""
  if os.path.isdir(destination):
    destination = os.path.join(destination, getFileBase(source) + getFileExt(source))
  # check extensions
  source_ext = getFileExt(source)
  dest_ext = getFileExt(destination)
  new_source = None
  if source_ext != dest_ext:
    if dest_ext == '.nii.gz':
      new_source = tempfile.mkstemp(dir=getFilePath(destination))[1]
      cmd=['nifti1_test','-zn1',source,new_source]
      execCmd(cmd)
      new_source += dest_ext
    elif dest_ext == '.nii':
      new_source = tempfile.mkstemp(dir=getFilePath(destination))[1]
      cmd=['nifti1_test','-n1',source,new_source]
      execCmd(cmd)
      new_source += dest_ext
    elif dest_ext == '.hdr':
      new_source = tempfile.mkstemp(dir=getFilePath(destination))[1]
      cmd=['nifti1_test','-n2',source,new_source]
      execCmd(cmd)
      new_source += dest_ext
    elif dest_ext == '.HEAD':
      new_source = tempfile.mkstemp(dir=getFilePath(destination))[1]
      cmd=['3dcopy',source,new_source]
      execCmd(cmd)
      new_source += '+orig' + dest_ext
    else:
      raise SbiaException("I don't know this extension '" + dest_ext + "'")
    
  if not move:
    if new_source:
      shutil.move(new_source,destination)
      try:
        shutil.move(switchExt(new_source),switchExt(destination))
      except SbiaException:
        pass
    else:
      shutil.copy2(source,destination)
      try:
        shutil.copy2(switchExt(source),switchExt(destination))
      except SbiaException:
        pass
  else:
    if new_source:
      os.remove(source)
      try:
        os.remove(switchExt(source))
      except SbiaException:
        pass
      source = new_source

    shutil.move(source,destination)
    try:
      shutil.move(switchExt(source),switchExt(destination))
    except SbiaException:
      pass

def WarnOverwrite(filename, verbose=1):
  """prints a warning and removes a file that will be overwritten"""
  if fileExists(filename):
    if verbose:
      print "!! Warning: " + filename + " exists and will be overwritten"
    os.remove(filename)
    try:
      os.remove(switchExt(filename))
    except SbiaException:
      pass

def maskNifti(filename, mask, outDir=None, suffix="_masked", erode=1, cleanup=True, verbose=0):
  """masks a Nifti or Afni volume using 3dcalc"""
  if not outDir or not fileExists(outDir,isDir=True):
    outDir = getFilePath(filename)

  root,ext= os.path.splitext(filename)
  if root.endswith("+orig"): # afni crap
    output = os.path.join(outDir,os.path.basename(root)[0:-5] + suffix)
    ext = "+orig"+ext
  else:
    output = os.path.join(outDir,root+suffix+ext)
    ext = ''
  WarnOverwrite(output+ext)
  
  if erode and isInteger(erode):
    erode=str(erode)
    maskCmd=["3dcalc","-a",filename,"-h",mask,"-prefix",output,\
    "-b","h["+erode+",0,0,0]","-c","h[-"+erode+",0,0,0]",\
    "-d","h[0,"+erode+",0,0]","-e","h[0,-"+erode+",0,0]",\
    "-f","h[0,0,"+erode+",0]","-g","h[0,0,-"+erode+",0]","-expr","a*step(h)*(1-amongst(0,b,c,d,e,f,g))"]
  else:
    maskCmd = ["3dcalc","-a",filename, "-h", mask, "-expr", "a*step(h)", "-prefix", output]
  execCmd(maskCmd,verbose=verbose)
  # re-add FDR info to header if necessary..
  fdrCmd = ["3drefit","-addFDR",output+ext]
  execCmd(fdrCmd)

  if cleanup:
    rmNifti(filename)

  return output+ext

def fixDataType(im, dtype):
  """converts im to dtype and replaces the image"""
  if not isinstance(im, NiftiImage):
    im = NiftiImage(im)
  nb = extractNumber(str(dtype))
  if nb == 8:
    dtype_arg = '-byte'
  if nb == 16:
    dtype_arg = '-short'
  if nb == 32:
    dtype_arg = '-float'
  # fix it
  im_in  = os.path.realpath(im.filename)
  im_fix = os.path.realpath(os.path.splitext(im_in)[0]+'_'+str(dtype)+".hdr")
  cmd=["3dcalc", "-a", im_in, "-expr", "a", "-prefix", im_fix, dtype_arg, "-fscale"]
  execCmd(cmd)
  # move it back
  shutil.move(im_fix, im_in)
  shutil.move(switchExt(im_fix), switchExt(im_in))
  
def readNrrdHeader(hdr):
  """reads a text .nhdr into a dictionary"""
  f = open(hdr,'r')
  nrrd_hdr_lst = f.read().split('\n')
  f.close()
  nrrd_hdr_dict = {}
  for i in nrrd_hdr_lst:
    if ':' in i:
      tmp = i.split(":")
      title = tmp[0]
      values = tryNumber(tmp[1].split())
      nrrd_hdr_dict.update({title:values})
  return nrrd_hdr_dict
  
###########################################################################
##########   NUMBER FUNCTIONS   ###########################################
###########################################################################

def isNumber(x, strOk=False):
  """returns True if x is a number (or list of numbers)"""
  if isList(x):
    return len(filter((lambda i : not isNumber(i, strOk=strOk)),x)) == 0
  if strOk: x = tryNumber(x)
  return isinstance(x, (int, long, float, floating))

def isInteger(x):
  """returns True if x is an integer (or list of integers)"""
  if isList(x):
    return len(filter((lambda i : not isInteger(i)),x)) == 0
  return isinstance(x, (int, long))

def isDecimal(x, strOk=False):
  """returns True if x is an float (or list of floats)"""
  if isList(x):
    return len(filter((lambda i : not isDecimal(i)),x)) == 0
  if strOk: x = tryNumber(x)
  return isinstance(x, (float, floating))
  
def isList(x):
  """returns True if x is some kind of a list"""
  return isinstance(x, (ListType, ndarray, tuple))
  
def tryNumber(x):
  """tries to return an appropriate number from x or just x if not a number"""
  if isList(x):
    return [tryNumber(i) for i in x]
  if not str(x).isdigit():
    try:
      return float(x)
    except ValueError, e:
      return x
  else:
    try:
      return int(x) # will return long if long int!
    except ValueError, e:
      return x

def extractNumber(x):
  """returns the first integer encountered in a string x (or list of strings) or None if no number is found"""
  if isList(x):
    return [extractNumber(i) for i in x]
  start=None
  x = str(x)
  for i in range(len(x)+1):
    if start!=None and (i==len(x) or not x[i].isdigit()): #return
      return tryNumber(x[start:i])
    elif start==None and x[i].isdigit(): #record the start
      start=i
  return None

###########################################################################
##########   ARRAY FUNCTIONS   ############################################
###########################################################################

def transpose(arr):
  """transposes a nested list (2D-array)"""
  return map(list, zip(*arr))

def unique(seq, blanks=True):
  """returns a list of unique elements from seq"""
  ret = []
  for e in seq:
    if e not in ret and (blanks or (not blanks and e)):
      ret.append(e)
  return ret

def reverse(arr):
  """returns a list of items in the reverse order"""
  return [arr[-i] for i in range(1,len(arr)+1)]
  
###########################################################################
##########   NIFTI ORIENTATION   ##########################################
###########################################################################

def isValidOrientation(orn):
  """
  checks whether a 3-letter orientation string contains (R or L) and (A or P) and (S or I)
  """
  if len(orn) != 3:
    return False
  orn = str(orn).upper()
  RL=False
  AP=False
  SI=False
  for i in [0,1,2]:
    if orn[i] in ("R","L"):
      RL = True
    elif orn[i] in ("A","P"):
      AP = True
    elif orn[i] in ("S","I"):
      SI = True
  if RL and AP and SI:
    return True
  return False

def getDcmOrientation(vectors):
  """
  convert directional cosines {dicom field (0020, 0037), relative to LPS} to a 3 letter orientation code
  adapted from http://www.dclunie.com/medical-image-faq/html/part2.html
  """
  if len(vectors) != 9 or not isNumber(vectors):
    raise SbiaException("Directional cosines needs to be a numerical list of length 9")
    
  orientation=""
  
  for axis in [vectors[0:3], vectors[3:6], vectors[6:9]]:
  
    getX = axis[0]
    getY = axis[1]
    getZ = axis[2]
    
    orientationX='R' if getX < 0 else 'L'
    orientationY='A' if getY < 0 else 'P'
    orientationZ='I' if getZ < 0 else 'S'

    absX = abs(getX)
    absY = abs(getY)
    absZ = abs(getZ)
    
    for i in [0,1,2]:
      if   absX > .3 and absX > absY and absX > absZ:
        orientation += orientationX
        absX=0
      elif absY > .3 and absY > absX and absY > absZ:
        orientation += orientationY
        absY=0
      elif absZ > .3 and absZ > absX and absZ > absY:
        orientation += orientationZ
        absZ=0
      else:
        break

  return orientation

def toOrn2From(orn):
  """
  Converts a three-letter orientation code from the 'to' convetion to the 'from' convention
  eg. LPS becomes RAI. AFNI supports the from orientation
  """
  if len(orn) != 3:
    raise SbiaException("Requiring a 3-letter orientation code as input")
  ret=""
  orn=orn.upper()
  for i in range(len(orn)):
    if orn[i] == 'L':
      ret += 'R'
    elif orn[i] == 'R':
      ret += 'L'
    elif orn[i] == 'P':
      ret += 'A'
    elif orn[i] == 'A':
      ret += 'P'
    elif orn[i] == 'S':
      ret += 'I'
    elif orn[i] == 'I':
      ret += 'S'
    else:
      raise SbiaException("Unrecognized orientation letter: " + orn[i])
  return ret
  
def getNiftiOrientation(Qform):
  """
  returns the three letter orientation of a Nifti file in the 'to' convention
  """
  if len(Qform) < 3 or len(Qform[0]) < 3 or not isNumber(Qform):
    raise SbiaException("Qform needs to be at least a 3x3 numerical matrix")
  
  orientation=""
  
  for axis in range(3):
  
    getX = Qform[axis][0]
    getY = Qform[axis][1]
    getZ = Qform[axis][2]
    
    orientationX='L' if getX < 0 else 'R'
    orientationY='P' if getY < 0 else 'A'
    orientationZ='I' if getZ < 0 else 'S'

    absX = abs(getX)
    absY = abs(getY)
    absZ = abs(getZ)
    
    for i in range(3):
      if   absX > .3 and absX > absY and absX > absZ:
        orientation += orientationX
        absX=0
      elif absY > .3 and absY > absX and absY > absZ:
        orientation += orientationY
        absY=0
      elif absZ > .3 and absZ > absX and absZ > absY:
        orientation += orientationZ
        absZ=0
      else:
        break

  return orientation
  
def nukeObliqueness(im):
  """
  gets rid of small rotations in qform
  """
  if not isinstance(im, NiftiImage):
    im = NiftiImage(im)
  qform = zeros([3,3])
  pixdims = im.getPixDims()[0:3]
  pos = 0
  for i in im.getQOrientation():
    if i == 1:   # Right-to-Left
      qform[0][pos] = pixdims[pos]
    elif i == 2: # Left-to-Right
      qform[0][pos] = -pixdims[pos]
    elif i == 3: # Posterior-to-Anterior
      qform[1][pos] = pixdims[pos]
    elif i == 4: # Anterior-to-Posterior
      qform[1][pos] = -pixdims[pos]
    elif i == 5: # Inferior-to-Superior
      qform[2][pos] = pixdims[pos]
    elif i == 6: # Superior-to-Inferior
      qform[2][pos] = -pixdims[pos]
    else:
      raise SbiaException("Orientation code " + str(i) + " not recognized!")
    pos +=1

  qformset = im.getQForm()
  qformset[0:3,0:3] = qform
  im.setQForm(qformset)
  return qformset
  #cmd=["3drefit","-deoblique",im]
  #return execCmd(cmd, verbose=0)

def setNiftiOrientation(im,orient):
  """sets the qform to indicate a three letter 'to' orientation eg. LAS"""
  if not isinstance(im, NiftiImage):
    im = NiftiImage(im)
  # AXIAL ORIENTAIONS
  if orient == "RAS":
    im.setQFac(1)
    im.setQuaternion([0,0,0])
  elif orient == "RAI":
    im.setQFac(-1)
    im.setQuaternion([0,0,0])
  elif orient == "RPI":
    im.setQFac(1)
    im.setQuaternion([1,0,0])
  elif orient == "RPS":
    im.setQFac(-1)
    im.setQuaternion([1,0,0])
  elif orient == "LAI":
    im.setQFac(1)
    im.setQuaternion([0,1,0])
  elif orient == "LAS":
    im.setQFac(-1)
    im.setQuaternion([0,1,0])
  elif orient == "LPS":
    im.setQFac(1)
    im.setQuaternion([0,0,1])
  elif orient == "LPI":
    im.setQFac(-1)
    im.setQuaternion([0,0,1])
  # SAGITTAL ORIENTATIONS
  elif orient == "ASR":
    im.setQFac(1)
    im.setQuaternion([0.5,0.5,0.5])
  elif orient == "AIR":
    im.setQFac(-1)
    im.setQuaternion([-0.5,-0.5,0.5])
  elif orient == "PIR":
    im.setQFac(1)
    im.setQuaternion([-0.5,0.5,-0.5])
  elif orient == "PSR":
    im.setQFac(-1)
    im.setQuaternion([0.5,-0.5,-0.5])
  elif orient == "AIL":
    im.setQFac(1)
    im.setQuaternion([-0.5,-0.5,0.5])
  elif orient == "ASL":
    im.setQFac(-1)
    im.setQuaternion([0.5,0.5,0.5])
  elif orient == "PSL":
    im.setQFac(1)
    im.setQuaternion([0.5,-0.5,-0.5])
  elif orient == "PIL":
    im.setQFac(-1)
    im.setQuaternion([-0.5,0.5,-0.5])
  # CORONAL ORIENTATIONS
  elif orient == "RSA":
    im.setQFac(-1)
    im.setQuaternion([0.70710676908493042,0,0])
  elif orient == "RIA":
    im.setQFac(1)
    im.setQuaternion([-0.70710676908493042,0,0])
  elif orient == "RIP":
    im.setQFac(-1)
    im.setQuaternion([-0.70710676908493042,0,0])
  elif orient == "RSP":
    im.setQFac(1)
    im.setQuaternion([0.70710676908493042, 0, 0])
  elif orient == "LIA":
    im.setQFac(-1)
    im.setQuaternion([-0, 0.70710676908493042, -0.70710676908493042])
  elif orient == "LSA":
    im.setQFac(1)
    im.setQuaternion([0, 0.70710676908493042, 0.70710676908493042])
  elif orient == "LSP":
    im.setQFac(-1)
    im.setQuaternion([-0, 0.70710676908493042, 0.70710676908493042])
  elif orient == "LIP":
    im.setQFac(1)
    im.setQuaternion([0, 0.70710676908493042, -0.70710676908493042])
  else:
    raise SbiaException("Orient code " + orient + " not recognized!")

###########################################################################
##########   MISC FUNCTIONS   #############################################
###########################################################################

def isBlank(x):
  """returns True if x is blank"""
  if isList(x):
    return len(filter((lambda i : not isBlank(i)),x)) == 0
  if not x:
    return True
  return False

def clean(s):
  """returns the stripped lower-case of a string"""
  if isList(s):
    return [clean(i) for i in s]
  return (str(s).lower()).strip()

def sanitize(s):
  """removes speacial characters from a string"""
  if isList(s):
    return [sanitize(i) for i in s]
  p = re.compile('[\$\\\/&><\s\[\]\(\)\*:^]')
  return p.sub('_',str(s).strip())

def logit(key, header, value=None, log=None, append=True, verbose=0):
  """
  logs a command to a Pyxel object or the screen
    key     - the row identifier
    header  - the column identifier
    value   - the cell value
    log     - supply a Pyxel object or a StringIO
    append  - append to or overwite if value already in the Pyxel object?
    verbose - log to screen if True
  """
  if hasattr(log, 'add'):
    log.add(key,header=header,value=value,mode=('smart_append'if append else 'overwrite'))
  elif hasattr(log, 'write'):
    log.write("| " + key + "\t (" + header + ")\t " + str(value)+"\n")
  else:
    if verbose > 0:
      print "LOG >> " + key + " / " + header + " = " + str(value)

def parseCols(incols):
  """parses the input column specification. For example expands 5,1-3 to [1,2,3,5]"""
  ret = []
  if incols != 'ALL': # use all columns = []
    cols = incols.split(',')
    for i in range(len(cols)):
      if "-" in cols[i] and cols[i].split("-")[0].isdigit() and cols[i].split("-")[1].isdigit():
        ret += range(int(cols[i].split("-")[0]),int(cols[i].split("-")[1])+1)
      else:
        ret += [cols[i]]
  return ret

def dtype2bytes(dtype):
  """returns number of bytes from numpy dtype object"""
  return extractNumber(str(dtype))/8
