# 
#  Copyright (C) 2011-2012,2014-2016,2019,2021  Smithsonian Astrophysical Observatory
#
#
#  This program 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.
#
#  This program 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 this program; if not, write to the Free Software Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#


# Oct -21 -2010

# pixlib.py
# this file


"""
a python module to interactivate pixlib-lib C functions.

Usage:

1> 'Pixlib' object Creation

    To create a Pixlib object, 'p' by making call with
    two arguments, telescope and geom-parameter,

      p = Pixlib(tele='tele', geom='geom.par')

    These arguments can be also omitted like
    
      p = Pixlib('tele')

    for leaving geom default to 'geom.par',

    or

      p = Pixlib()

    for additional telescope default to 'chandra'

  Note that, both 'tele' and 'geom' are immutable attributes of the
  current object or their values are not updatable. Once the object is
  launched, the session of the object is to hold the values of telescope and
  geom until the end.


2> The Object Attribute Assignments

    The assignment syntax is

      object.attr=value

    For example,
    
      p.detector='hrc-s'
    assign object 'p' attribute of 'detector' with the string.

      p.aimpoint=-0.78234819833843305, 0.0, -233.5924630914318
    assign the 'p' attribute of 'aimpoint' with 3-D floats in tuple.

3> The Method Properties

   Followed are examples of using property-methods
   
   
     p.aimpoint=<string>       - set aimpoints name in string
     p.aimpoint                - get the current aimpoint

     p.detector=<string>       - set detector name in string
     p.detector                - get the current detector

     p.fpsys=<string>          - set FP system name in string 
     p.fpsys                   - get the current Fp system in string

     p.isokay=<int>            - set the current run status with int value
     p.isokay                  - get the current run status

     p.close                   - end the current session

   Followed are examples of using transformation-methods

     fpc=p.chip2fpc((3,(123.0,456.0))
     id,chip=p.fpc2chip(fpc)

     mnc=p.fpc2mnc(fpc)
     fpc=p.mnc2fpc(mnc)

     sky=p.app_aspect(fpc,(0.2,0.3,0.4))
     fpc=p.deapp_aspec(sky)
     
     

4> Get start
   % python
   >>> from pixlib import *
   >>> pixhelp()

5>  Show usage
   >>> usage()

"""

#------------  Beginning of Template ----------------------
# Support python 2 and 3
from __future__ import print_function

# import packages
from types import *      #for python types operation
from ctypes import *
import sys,os,math
import collections       # for ordered dictionary


#=============================================================================
#
#   --- Load Library ---
#
#=============================================================================
def loadLibrary(name):
    '''load the shared library and return the handle'''

    DEFAULT_MODE = RTLD_LOCAL
    if os.name == "posix" and sys.platform == "darwin":
        # Helen @ Dec 6 2012
        # On OSX, we use RTLD_GLOBAL as default mode
        # because RTLD_LOCAL does not work at least on
        # the following sequence of importing libraries:
        # 
        # import caldb4, pixlib
        # 
        # where a "Expected flat namespace" error occurs when 
        # both caldb4lib.py and pixlib.py link to the run-time shared library, libcaldb4.dylib
        # similar issues discussed elsewhere, like
        # http://mail.python.org/pipermail/python-bugs-list/2002-February/010167.html
        # http://forums.macrumors.com/archive/index.php/t-639889.html
        # or
        # /proj/xena/ciaod/osx64/ciao_1205/ots/lib/python2.7/ctypes/__init__.py

        DEFAULT_MODE = RTLD_GLOBAL  

   
    # simplify the lib-name
    if sys.platform[:3] in ('dar', 'os2'): # Mac
        libname = 'lib%s.dylib' % name
    else:                                  # assume UNIX-like
        libname = 'lib%s.so' % name
    libpath = os.environ['ASCDS_INSTALL'] + '/lib'


    # loooking up the named shared library
    # use 'CDLL' from ctypes.util
    # returns the pathname of a library, or None.
    abslibname=libpath +'/'+ libname
 
    try:
        _libc=CDLL(abslibname, DEFAULT_MODE) 
        if _libc != None:
            _libc.pix_init_pixlib( b"chandra", b"geom")
            if _libc.pix_is_init():
                _libc.pix_close_pixlib()
                return _libc
        else:
            raise ImportError("Unable to import pixlib")
    except ImportError:
        print ("Error: unable to import pixlib")


_libc =  loadLibrary(('pix'))


#=============================================================================
#
#   handle fs-string, bytes-string, and unicode-string for Py3 migration, incl
#    convert_to_ascii()
#    isstring()
#
#=============================================================================

def isstring( val ):
    """ return true if it is string in fs-string or unicode-str or bytes-strin """

    # The bytes and unicode types are 'bytes' and 'str' 
    # str is unicode string in Py3
    # ---------- Python 2, both bytes and str represent the 'str' string type, ----------
    # >>> import sys
    # >>> sys.version_info.major
    # 2
    # >>> s="hello"
    # >>> u=u"hello"
    # >>> b=b"hello"
    # >>> type(s)
    # <type 'str'>
    # >>> type(u)
    # <type 'unicode'>
    # >>> type(b)
    # <type 'str'>
    # >>>
    # 
    # ---------- Python 3, both unicode and str represent the 'str' string type. ----------
    # >>> sys.version_info.major
    # 3
    # >>> s="hello"
    # >>> u=u"hello"
    # >>> b=b"hello"
    # >>> type(s)
    # <class 'str'>
    # >>> type(b)
    # <class 'bytes'>
    # >>> type(u)
    # <class 'str'>
    # >>>

    if sys.version_info.major >= 3:     # for Python 3, unicode and fs-string are same, bytes not recognizable
        if isinstance(val, str) or isinstance(val, bytes):
            return True
    else:                               # for Python 2, bytes and fs-string are same, unicode detecable
        if isinstance(val, str) or isinstance(val, unicode)  or isinstance(val, bytes):
            return True

    return False

def convert_to_ascii( val ):
    """  convert bytes-string and unicode-string to ascii-string and return the ascii.   """
    if not isstring(val):
        raise PixError("Error: not strings  at '"+str(val)+"'.")

    if sys.version_info.major >= 3:     # for Python 3, unicode and sf-string are same, bytes not recognizable
        if isinstance(val, bytes) :
            val=val.decode()

    return str(val)

#=============================================================================
#
#   --- The Exception/Error handling Class ---
#
#=============================================================================
class PixError(Exception):
  """
  pixlib exception and error-handling class
  """
  def __init__(self, value):
    self.value = str(value)
  def __str__(self):
    return repr(self.value)


'''
Jan 23
  it seems works if using
   _msg=create_string_buffer('\000' * 120)
 to replace
   _msg=c_char_p(120)
   _msg.value=" "   # not _msg.value=""

Jan 20,2012
avoid use pix_get_error() function which cause segfault unpredictably
'''
def PixGetError():

  # create a mutable memory blocks
  # to pass them to functions expecting pointers
  # to mutable memory.
  _pixemsg =create_string_buffer(b'\000' * 1023)
  _msg=b""

  # get the oldest error message from the stack
  if(_libc.pix_get_error(_pixemsg) !=0 ):
      _msg = _pixemsg.value
      if _msg.find(b"Warning:") != -1 or  _msg.find(b"WARNING:") != -1:
          _msg = _msg.capitalize()
          _msg=_msg.replace(b"Warning:", b"Error:")
          
  # get the rest error messages
  while( _libc.pix_get_error(_pixemsg) != 0):
      _msg += b", " +_pixemsg.value
    
  return _msg.decode()   # return 'str' type


def PixDimnType(_name, coord, dimn=2, _type=float, _numtype=True):

    name=convert_to_ascii(_name)
    rtnmsg=str("")

    if not isinstance(coord, (list,tuple) ) :   # validate list or tuple array type 
        rtnmsg=str("Error: need 'list' or 'tuple' type for '" + name + "' not the type of '" + str(type(coord)) + "'.") 
    elif len(coord) < dimn :                    # validate dimension, at least  2-D
        rtnmsg=str("Error: need "+str(dimn)+"-D for '" + name+"' but " + str(len(coord))+"-D input.")
    elif _numtype and dimn>=1:                  # validate numeric types except string stype
        i=0
        while i<dimn :
            if isstring( coord[i] ):   
                rtnmsg=str("Error: invalid data type of '" + name+"', '" + coord[i] + "', in "+str(coord)+".")
                i=dimn
            i += 1
    elif dimn>=1 :                              # validate the exact type, _float
        i=0
        while i<dimn :
            if not isinstance(coord[i], _type):   
                rtnmsg=str("Error: invalid data type of '" + name+"', '" + str(coord[i]) + "', in "+str(coord)+".")
                i=dimn
            i += 1

    return rtnmsg

def set_decim_pts(vals, n=9):
    """ set number of decimal points for float number."""

    if n <= 0 or not vals or isinstance(vals, int):
        return vals
    
    try: 
        if isinstance(vals, float):
            return float('%.*f' % (n, vals))

        if len(vals)==3:
            return float('%.*f' % (n, vals[0])),float('%.*f' % (n, vals[1])),float('%.*f' % (n, vals[2])),
        elif len(vals)==2:
            return float('%.*f' % (n, vals[0])),float('%.*f' % (n, vals[1]))
        else:
            return float('%.*f' % (n, vals[0]))
    except:
        raise OSError("failed to set decimal point of float number")


class Pixlib(dict):
  """
  Interface pixlib coordinates transformation
  """

  #  __slots__ = ['_stat','_mydict', '__dict__']

  _confLst = ["telescope", "geom", "CALDB"]
  _defaLst = ["detector", "aimpoint","tdetsys", "fpsys","gdpsys","grating"]

  def __print__(self):
    """ print the current Class status."""
    
    attrLst = self.__class__._confLst
    
    classinfo = "\n\t%s\
    \n\tThe '%s' configuration:\n" % (50*'=',self.__class__.__name__)
    classinfo = classinfo+"\n".join(["\t%s: '%s'" % (key.rjust(10), self.__dict__[key]) for key in attrLst])
    classinfo = classinfo+"\n\t%s\n" %(50*"=")

    return classinfo

  def __str__( self): return self.__repr__()
  def __repr__(self):
      if self._stat == -1:
          return "\nWARNING: the current session has been closed or not initiated."
      elif not self.isokay :
          return self.errmsg+"\n"
      else :
          config   = self.__print__()
          default  = self._lstdefs()
          curinput = self._lstattrs()
          return config + default +curinput

  def isokay(self):
    """Get/Set error status:
    
        'Get' method return:
            1: there is no error or True
            0: there is an error or False
           
        'Set' method input:
            1: set the current status no error, or True==1
            0: set the current status error/warning, or False==0
               
     """
    _errmsg=self.errmsg

    if _errmsg.find("ERROR:")  != -1 or _errmsg.find("Error:") != -1 :      # find a "error"
      return 0                                                              # return not-okay
    elif _errmsg.find("Warning:") != -1 or _errmsg.find("WARNING:") != -1:  # find a "warning"
      return 1                                                              # return ok, leave it as is
    elif isinstance(_errmsg, int):                                          # return whatever
      return _errmsg
    else:
      return 1                                                               # return okay for the rest info

  def _setErrmsg(self, emsg):
      if isstring(emsg):
          #    if len(self.errmsg):
          #        self.errmsg += "\n"+ emsg
          #    else:
          self.errmsg = emsg  # the current errmsg is overwritting the previous one
      elif isinstance(emsg, int):
          if emsg==1:                    
              self.errmsg=""     # clean the buffer
    
  isokay=property(isokay, _setErrmsg)
    
  def __init__(self, tele="chandra", geom="geom.par"):
    """              
      Launch pixlib.

      Arguments:
                tele - string of telescope,  default to 'chandra'
                geom - string of pixlib parameter file, dfault to 'geom'                
      Return: pixlib object
      
      Notes:
             At API level,
       
             1> the instance of this method is a call of the named class
             that shall automatically launch the '__init__' method.
             For example, the act of
       
             p = Pixlib('mytele', 'mygeom)
       
             is to instaniate an object, 'p', after activating ' __init__' internally.
       
             2> ignore the first argument, 'self', of the function which is
             Python 'Class' internal property.
    """              

    try:
        _tele = convert_to_ascii(tele).encode()
        _geom = convert_to_ascii(geom).encode()

        self._stat = _libc.pix_init_pixlib(_tele, _geom)
        pix = _libc.pix_is_init()
        if self._stat != 0 or pix == 0:
            self._stat = -1
            raise PixError("\nError: failed to launch pixlib at ('"+tele+"', '"+geom+"').")
        
        self.telescope=tele
        self.geom=geom
        self.CALDB = os.environ['CALDB']
        self._mydict = collections.OrderedDict() 
        self.isokay=True

    except PixError as e:
        self.isokay=e.value  
        print(e.value)           # instruct to re-launch pixlib 

  def _lstattrs(self):

    lstattrs=""
    for key in self._mydict:
      val=self._mydict[key]
      if isstring(val):
          lstattrs=lstattrs + "\t%s = '%s'\n" % (key.rjust(10), val)
      elif isinstance(val, (tuple,list)) and 'chip' in key:
          id,chip=val 
          fval= set_decim_pts(chip)  
          lstattrs=lstattrs + "\t%s = %2i,%s\n" % (key.rjust(10), id,fval)
      elif isinstance(val, (tuple,list)) and 'mirror' in key:
          stg,theta=val 
          fstg  = set_decim_pts(stg)  
          ftheta= set_decim_pts(theta,3)  
          fval=fstg,ftheta
          lstattrs=lstattrs + "\t%s = %s\n" % (key.rjust(10), fval)
      else:
          fval= set_decim_pts(val)  
          lstattrs=lstattrs + "\t%s = %s\n" % (key.rjust(10), fval)

    if len(lstattrs) :
      lstattrs="\n\t***** The current inputs *****\n"+lstattrs
    return lstattrs
  
    
  def _lstdefs(self):
    """
    """
    
    attrLst = self.__class__._defaLst  #tbdone on the list
    
    lstdefs=""
    for key in attrLst:
      if not key in self._mydict:
        if key =="detector":
          val=self.detector   #get the latest detector
        elif key == "aimpoint":
          val=self.aimpoint   #get the latest aimpoint
        elif key == "tdetsys":
          val=self.tdetsys
        elif key == "fpsys":
          val=self.fpsys
        elif key == "gdpsys":
          val=self.gdpsys
        elif key == "grating":
          val=self.grating

        lstdefs=lstdefs + "\t%s = '%s'\n" % (key.rjust(10), val)
       
    if len(lstdefs):
      lstdefs="\n\t***** The current defaults *****\n"+lstdefs

    return  lstdefs
  
  #-----------------------------
  # use property type
  # get or execute  obj.attr
  # set             obs.attr=arg1,arg2,...
  #------------------------------
  @property
  def close(self):
    """Close the current pixlib  interface session."""

    if self._stat == 0:
        _libc.pix_close_pixlib()
        self._stat = -1
 
  #----------------------------------------------------------
  def detector(self):
    """
    Get/Set detector name in string.
    """
    #---------------
    # Note:
    # all Python types except integers, strings, and unicode strings
    # have to be wrapped in their corresponding ctypes type, so that
    # they can be converted to the required C data type:
    #  printf=_libc.printf
    #  printf("%f bottles of beer\n", 42.5) not working but
    #  printf("%f bottles of beer\n", c_double(42.5))
    #---------------
    #  if self._stat != 0:
    #    return None
    
    var=create_string_buffer(b'\000' * 20) # ctypes function
    try:      
      self._stat=_libc.pix_get_detector_name(var)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError())
      self.isokay=True    
      return var.value.decode()  
    except PixError as e:
      self.isokay=e.value   # set errormsg
      print(e, file=sys.stderr)   # throw exception
      return ""

  def _set_detector(self, det):

    try:     
      # convert to fs-string for unicode- and bytes- strings
      det = convert_to_ascii(det)
      self._stat=_libc.pix_set_detector(det.encode())
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError() +", at '"+det+"'.")
      self._mydict['detector'] = det
      self.isokay=True
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
   
  detector=property(detector, _set_detector)
  
  #--------------------------------------------------      
  
  def aimpoint(self):
    """
    Get/Set aimpoint name in string or set 3-D aimpoint position in float.
    """
        
    aim=create_string_buffer(b"Hello aim", 20) # ctypes function
    try:      
      self._stat=_libc.pix_get_aimpoint_name(aim)
      if self._stat != 0:
          self._stat = 0
          raise PixError(PixGetError() +", at '"+aim+"'.")
      self.isokay=True
      return aim.value.decode()
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return ""
    
  def _set_aimpoint(self, aim):
    """
    Set aimpoint in string
    or 
    set 3-D aimpoint position [mm] in float
    """

    try:      
        if isinstance(aim, (tuple,list)):
            self.aimcoord=aim
        else:
            # convert to fs-string for unicode- and bytes- strings
            aim = convert_to_ascii(aim)
            self._stat=_libc.pix_set_aimpoint(aim.encode())
            if self._stat != 0 :
                self._stat = 0  # keep the current configs
                raise PixError(PixGetError()  +", at '"+str(aim)+"'." )

            self.isokay=True
            self._mydict['aimpoint'] = aim
            if "aimcoord" in self._mydict.keys():  # remove the 'aimcoord' attr if 'aimpoint' set
                del self._mydict['aimcoord']
    except PixError as e:
       self.isokay=e.value
       print(e, file=sys.stderr)   # throw exception

  aimpoint=property(aimpoint, _set_aimpoint)

  #--------------------------------------------------
  def aimcoord(self):
    """
    Get/Set 3-D aimpoint position in float.
    """
     
    var = (c_double * 3 )(0.0,0.0,0.0)
    try:      
        self._stat=_libc.pix_get_aimpoint_by_value(var)
        if self._stat != 0:
            self._stat = 0         # keep the current configs
            raise PixError(PixGetError())

        self.isokay=True
        return var[0],var[1], var[2]
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception
        return (None,None,None)
  
  def _set_aimcoord(self, aim):
    """
    set 3-D aimpoint position
    """

    try:      
        emsg=PixDimnType("aimcoord", aim, 3)
        if emsg.find("Error:") != -1:
            raise PixError(emsg+", at "+str(aim)+"'.")

        aim3 = (c_double * 3)(aim[0],aim[1],aim[2])
        self._stat=_libc.pix_set_aimpoint_by_value(aim3)
        if self._stat != 0:
            self._stat = 0  # keep the current configs
            raise PixError(PixGetError()+": '"+aim+"'.")
  
        self.isokay=True
        self._mydict['aimcoord'] = aim[0],aim[1],aim[2]
        self._mydict['aimpoint'] = self.aimpoint
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception

  aimcoord=property(aimcoord, _set_aimcoord)
  
  #--------------------------------------------------
  
  def tdetsys(self):
    """
    Get/Set Tiled-Detector system name in string.
    """

    tdetsys=create_string_buffer(b"Hello tdet", 20) # ctypes function
    
    try:      
      self._stat=_libc.pix_get_tdetsys_name(tdetsys)
      if self._stat != 0:
        self._stat=_libc.pix_get_tdetsys_default(tdetsys)
        if self._stat != 0:
          self._stat = 0
          raise PixError(PixGetError() +", '"+tdetsys+"'.")
      self.isokay=True
      return tdetsys.value.decode()
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception
        return ""
   
  def _set_tdetsys(self, tdet):
    """Set Tiled-Detector system in string."""
    try:      
      tdetsys = convert_to_ascii(tdet)
      self._stat=_libc.pix_set_tdetsys(tdetsys.encode())
      if self._stat != 0: 
          self._stat = 0
          raise PixError(PixGetError() +", at '"+tdetsys+"'.")     
      self._mydict['tdetsys'] = tdetsys
      self.isokay=True
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception


  tdetsys = property(fget=tdetsys, fset=_set_tdetsys)

  #--------------------------------------------------
  
  def gdpsys(self):
    """
    Get/Set Grating Dispersion Plane (GDP) system in string.
    """

    gdpsys=create_string_buffer(b"Hello gdp", 20) # ctypes function
 
    try:      
        self._stat=_libc.pix_get_gdpsys_name(gdpsys)
        if self._stat != 0:
            self._stat=_libc.pix_get_gdpsys_default(gdpsys)
            if self._stat != 0:
                self._stat = 0
                raise PixError(PixGetError())      
        self.isokay=True
        return gdpsys.value.decode()
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception
        return ""
  
  def _set_gdpsys(self, var):

    try:      
      gdpsys=convert_to_ascii(var)
      self._stat=_libc.pix_set_gdpsys(gdpsys.encode())
      if self._stat != 0:
          self._stat = 0
          raise PixError(PixGetError()+", at '"+gdpsys+"'.")      
      self._mydict['gdpsys'] = gdpsys
      self.isokay=True
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
       

  gdpsys=property(gdpsys,  _set_gdpsys)
  
   #--------------------------------------------------
  @property
  def grt_prop(self):
    """
    Get grating propery, (period[Angstrom], angle[degree]) in float.
    """
    
    out= (c_double * 2)(0.0, 0.0)

    try:      
      self._stat=_libc.pix_get_grating_property(out)
      if self._stat != 0:
          self._stat = 0
          raise PixError(PixGetError())
      self.isokay=True
      return out[0],out[1]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return None,None

  def grt_order(self):
    """
    Get/Set grating order in integer.

    """
    
    if 'grt_order' in self._mydict.keys():
        return self._mydict['grt_order']
    else:
        return None
 
  
  def _set_grtorder(self, gorder):

    """
    set grating order in integer.
    """
    try:
      if not isinstance(gorder,int): 
          raise PixError("Error: need 'int' type for grt-order.")
      self._stat=_libc.pix_set_grating_order(gorder)
      if self._stat != 0:
          self._stat = 0
          raise PixError(PixGetError()+", at '"+str(gorder)+"'.")      
      self.isokay=True
      self._mydict['grt_order'] = gorder
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception

   
  grt_order=property(grt_order, _set_grtorder)
  
 #--------------------------------------------------

  def grating(self):
    """
    Get/Set grating arm in string
    or
    set grating arm string and grating order integer in tuple/list.
    """

    grating=create_string_buffer(b"Hello grating", 20) # ctypes function

    try:      
      self._stat=_libc.pix_get_grtarm_name(grating)
      if self._stat != 0:
        err=PixGetError()
        self._stat=_libc.pix_get_grtarm_default(grating)
      if self._stat != 0:
        self._stat = 0
        PixGetError()    # cleaning up pix-lib error stack
        raise PixError(err)  
      self.isokay=True
      return grating.value.decode()
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return ""


  def _set_grating(self, var):
    """
    Set grating arm in string or set (arm,order) in tuple/list.

    """

    try:      
      arm=""
      if isinstance(var, tuple) or  isinstance(var, list):
        if len(var) >= 2:
            #if isinstance(var[0], str) and isinstance(var[1], int):
            if isstring(var[0]) and isinstance(var[1], int):
                arm = convert_to_ascii(var[0])
                self._stat= self._set_grtorder(var[1])
                self.grt_order=var[1]
      elif isstring(var):
        arm = convert_to_ascii(var)
      else:
        raise PixError("Error: invalid syntax. Try obj.grating=arm-string or obj.grating=arm-string,order-int ")
        
      self._stat=_libc.pix_set_grating(arm.encode())
      if self._stat != 0: 
          self._stat = 0
          raise PixError(PixGetError()+", at '"+str(var)+"'.")
      self._mydict['grating'] = arm
      self.isokay=True
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
   
  grating=property(grating, _set_grating)

  #--------------------------------------------------
  @property
  def flength(self):
    """
    Get telescope focal length [mm] in float.    
    """
    out = (c_double *1 )(0.0)
    
    try:
        self._stat = _libc.pix_get_flength(out)
        if self._stat < 0.0:
            self._stat=0 
            raise PixError(PixGetError())
        self.isokay=True
        return out[0]
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception
        return  None
   
  
  #--------------------------------------------------
  
  def grt_zo(self):
    """
    Set FC-GZO transeformation matrices at for 3-D Focus Coordinates at Zero-Order or 
    Get 3-D Grating Zero Order coordinates in mm.

    """
    
    
    if 'FC_ZO' in self._mydict.keys():
        fc_zo = self._mydict['FC_ZO']
        inp = (c_double * 3)(fc_zo[0],fc_zo[1],fc_zo[2])
        gzo = (c_double * 3)(0.0,0.0,0.0)
        self._stat = _libc.pix_fc_to_gzo(inp, gzo) # calc gzo from fc2gzo_transf matrix
        if self._stat != 0:
            self._stat = 0
            raise PixError(PixGetError()+", at '"+str(fc_zo)+"'.")
        self.isokay=True
        return gzo[0],gzo[1],gzo[2]
    else:
        return (None, None, None)
  
  def _set_gzo(self, fc_zo):
    """
    setup FC-to-GZO transformation at focus coordinates at zero-order grating (fc_zo) in mm,
    so does GZO-to-FC transformation, and so does zero-order grating coordinates (grt_zo) in mm.

    """
    
    try:
        emsg=PixDimnType("FC_ZO", fc_zo,3)
        if emsg.find("Error:") != -1:
            raise PixError(emsg)

        inp = (c_double * 3)(fc_zo[0],fc_zo[1],fc_zo[2])
        self._stat = _libc.pix_set_gzo(inp)   # setup fc2gzo_transf and gzo2fc_transf matrices
        if self._stat != 0:
            self._stat = 0
            raise PixError(PixGetError()+", at '"+str(fc_zo)+"'.")
        self.isokay=True
        self._mydict['FC_ZO']=fc_zo[0],fc_zo[1],fc_zo[2]
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception

  grt_zo=property(grt_zo,_set_gzo)
  
  
  #--------------------------------------------------
  def fpsys(self):
    """
    Get/Set FP system in string.
    """
    fpsys=create_string_buffer(b"Hello fpsys", 20)   # ctypes function

    try:      
      self._stat=_libc.pix_get_fpsys_name(fpsys)
      if self._stat != 0:
          err=PixGetError()    # retain the first encountering error
          self._stat=_libc.pix_get_fpsys_default(fpsys)
      if self._stat != 0:
          self._stat = 0
          PixGetError()     #  clean up the error stack
          raise PixError(err + ", at '"+fpsys+"'.")
      self.isokay=True
      return fpsys.value.decode()
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return ""                # return empty string for failure
   
  
  def _set_fpsys(self, var):
    """
    Set FP system in string.
    """
    try:      
      fpsys = convert_to_ascii(var)
      self._stat=_libc.pix_set_fpsys(fpsys.encode())
      if self._stat != 0:
          self._stat = 0
          raise PixError(PixGetError()+", at '"+fpsys+"'.")
      self._mydict['fpsys'] = fpsys
      self.isokay=True
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception


  fpsys=property(fpsys, _set_fpsys) 


  #--------------------------------------------------
  @property 
  def fp_scale(self):
    """Get FP scale [arcsecs] in float."""

    var = (c_double *1 )(0.0)

    try:
      self._stat=_libc.pix_get_fp_scale_in_asec(var)
      if self._stat != 0:
          self._stat = 0
          raise PixError(PixGetError())
      self.isokay=True
      return var[0]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return None

   
  #--------------------------------------------------
  def mirror(self):
    """
    Get/Set 3-D stage position [mm] and stage angle theta [arcsecs] in float.

    Arguments for 'Set' method:
             stg_ang - stage position and angle formated
                       in ((stg_x,stg_y,stg_z), theta) tuple, where
                       stg   - 3-D stage posion [mm] in float
                       theta - stage angle [arcsecs] along Z-axis in float

             example of setting mirror:
                     stg=0.2,0.3,0.4
                     the=2.3
                     o.mirror=(stg,the)
    """

    if 'mirror' in self._mydict.keys():
        return  self._mydict['mirror']  # formated in ( (stage 3-d positon), (stage angle) )
    else:
        return (None, None,None),None
    

  def _set_mirror(self, stg_theta):
    """
    Set stage position [mm] and stage angle theta [arcsecs].
    
    Arguments:
     stg_ang - stage position and angle formated in ((stg_x,stg_y,stg_z), theta)
               where 
               stg   - 3-D stage posion [mm] in float
               theta - stage angle [arcsecs] along Z-axis in float

               an example of setting mirror:
                     stg=0.2,0.3,0.4
                     the=2.3
                     o.mirror=(stg,the)
    
    """
    
    try:

      # check the format ((stg_x,stg_y,stg_z), theta) in size 2 
      if len( stg_theta ) != 2 :
          raise PixError("Error: expected input syntax: '((pos1,pos2,pos3), ang)' .")
 
      stg,theta   = stg_theta  # unpacked them 
      err = PixDimnType("stage-pos", stg,3)
      if err.find("Error:") != -1:
          raise PixError(err)

      if isinstance(theta, str): 
          raise PixError("Error: need non-string type for stage-angle in '" + str(stg_theta) + "'.")
 

 
      #--------------------------------------------------------------
      # convert any non-string type to python float type first,
      #         including float64 or float32 NP types
      #--------------------------------------------------------------
      theta = float(theta)

      pos = (c_double *3)(stg[0],stg[1],stg[2])
      ang = (c_double *3) (0.0, 0.0, theta)
      mpy = (c_double *2) (0.0,0.0)

      self._stat =_libc.pix_set_mirror(mpy, pos, ang)
      if self._stat != 0 :
          self._stat = 0 
          raise PixError(PixGetError() +", at '"+str(stg_theta)+"'.")
      self._mydict['mirror']=((pos[0],pos[1],pos[2]), theta)
      self.isokay=True
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception


  mirror=property(mirror, _set_mirror)

  #--------------------------------------------------
  # coords transfer in regular function format
  # return (id,(chip_x,chip_y))
  #--------------------------------------------------
  def _fpc2chip_extend(self, fpc):

      try:
          g_origin = (c_double * 3)(0.0,0.0,0.0)
          self._stat=_libc.pix_get_OTG_origin(g_origin)
          if self._stat != 0:
              self._stat = 0   
              raise PixError(PixGetError())
      
          fpc   = (c_double * 2)(fpc[0],fpc[1])
          mnc_ray  = (c_double * 3)(0.0,0.0,0.0)
          self._stat=_libc.pix_fpc_to_mnc(fpc, mnc_ray)
          if self._stat != 0:
              self._stat = 0
              raise PixError(PixGetError() +", at '"+str(fpc)+"'.")
      
          chip      = (c_double * 2)(0.0, 0.0)
          chip_id   = (c_int * 1) (0)
          lsi       = (c_double * 3)(0.0,0.0,0.0)
          self._stat=_libc.pix_ray_to_detector(g_origin, mnc_ray, chip_id,chip,lsi )
          if self._stat != 0:
              self._stat = 0
              raise PixError(PixGetError() +", at '"+str(fpc)+"'.")

          self._stat=_libc.pix_fpc_to_extended_chip( fpc, chip_id[0], chip );
          if self._stat != 0:
              self._stat = 0
              raise PixError(PixGetError() +", at '"+str(fpc)+"'.")
          self._mydict['fpc'] = fpc[0],fpc[1]   
          self.isokay=True
          return chip_id[0],(chip[0],chip[1])
      except PixError as e:
          self.isokay=e.value
          print(e, file=sys.stderr)   # throw exception
          return -1, (None, None)     # return -1 or NONE for failure

  def fpc2chip(self, fpc):
    """
    Transfer 2-D fpc coords to chip ID and  2-D chip coords.
    
    Arguments:
              fpc   - 2-D FPC coords in float
    Return: (chip_id, (chip_x,chip_y)) in tuple
            where chip_id is in integer and chip coords in float.

    """

    try:
        err=PixDimnType("fpc", fpc)
        if err.find("Error:") != -1:
            raise PixError(err)

        dfpc = (c_double * 2)(fpc[0], fpc[1])
        id   = (c_int * 1) (0)
        out  = (c_double * 2)(0.0, 0.0)
        self._stat=_libc.pix_fpc_to_chip(dfpc, id, out)
        if self._stat != 0:   # 0=PIX_FOOD  others=fail            
            err=PixGetError() # reserved the error where it started from
            self._stat = _libc.pix_fpc_to_extended_chip_and_id(dfpc, id, out)
            if self._stat == -1: # 3-state status # 0=on-chip ->good,  1=off-chip ->good,  -1=fail ->notgood
                self._stat = 0
                PixGetError()    # empty all message from the stack
                raise PixError(err +", at '"+str(fpc)+"'.")
        self._mydict['fpc'] = fpc[0],fpc[1]   
        self.isokay=True
        return id[0],(out[0],out[1])
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception
        return -1,(None, None)      # return -1 or NONE for failure


  def fpc2chipCoords(self, id_n_fpc):
    """
    Transfer 2-D fpc coords to 2-D Chip coords.
    
    Arguments:
         id_n_fpc   - chip ID in integer and 2-D fpc coords in float
                          in (id, (fpc_x,fpc_y)) tuple or list.           
    Return: (chip_id, (chip_x,chip_y)) in tuple
            where chip_id is in integer and chip coords in float.

    """

    try:
        # check the format (id,(fpcx,fpcy)) in size 2 
        if len( id_n_fpc ) != 2 :
            raise PixError("Error: expected input syntax: '(id, (fpcx,fpcy))' .")
 
        (id,fpc) = id_n_fpc # unpacked
 
        err=PixDimnType("fpc", fpc)
        if err.find("Error:") != -1:
            raise PixError(err)
        if not isinstance(id, int):
            raise PixError("Error: need 'int' type for chip-id not the type of '"+str(type(id))+"'." )

        fpc = (c_double * 2)(fpc[0], fpc[1])
        out = (c_double * 2)(-99, -99)
        self._stat = _libc.pix_fpc_to_extended_chip(fpc, id, out)
        if self._stat != 0 : 
            self._stat = 0
            raise PixError(PixGetError() +", at '"+str(id_n_fpc)+"'.")
        self._mydict['fpc'] = fpc[0],fpc[1]   
        self.isokay=True
        return id,(out[0],out[1])
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception
        return -1,(None, None)      # return -1 or NONE for failure


  def chip2fpc(self, id_n_chip):
    """
    Transfer 2-D Chip coords to 2-D FPC coords, incl extended chip.
    
    Arguments:
              id_n_chip - chip ID in integer and 2-D chip coords in float
                          in (id, (chip_x,chip_y)) tuple or list.           
    Return: (fpc_x,fpc_y) in float

    """
    try:

        # check the format (id,(chipx,chipy)) in length 2 
        if len( id_n_chip ) != 2 :
            raise PixError("Error: expected input syntax: '(id, (chipx,chipy))' .")
 
        id,chip= id_n_chip # unpacked
        err=PixDimnType("chip",chip)
        if err.find("Error:") != -1:
            raise PixError(err)
        if not isinstance(id, int):
            raise PixError( "Error: need 'int' type for chip-id not the type of '"+str(type(id))+"'." )

        chip = (c_double * 2)(chip[0], chip[1])
        out  = (c_double * 2)(0.0, 0.0)
        id0=id
        self._stat=_libc.pix_chip_to_fpc(id, chip, out)
        if self._stat != 0:
            id = id0
            err=PixGetError()   # retain the first error before next call
            self._stat = _libc.pix_extended_chip_to_fpc(id, chip, out)
            if self._stat != 0: 
                self._stat = 0
                PixGetError()   # flush error out of stack
                raise PixError(err  +", at '"+str(id_n_chip)+"'.")
        self._mydict['chip'] = id,(chip[0],chip[1])
        self.isokay=True
        return out[0],out[1]
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception
        return None,None            # return NONE for failure 

  

  #--------------------------------------------------

  def chip2tdet(self, id_n_chip):
    """
    Transfer 2-D Chip coords to 2-D TDET coords.
    
    Arguments:
              id_n_chip - chip ID in integer and 2-D chip coords in float 
                          in (id, (chip_x,chip_y)) tuple or list              
    Return: (tdet_x,tdet_y) in float

    """

    try:
        
      # check the format (id,(chipx,chipy)) in size 2 
      if len( id_n_chip ) != 2 :
          raise PixError("Error: expected input syntax: '(id, (chipx,chipy))' .")
 
      id,chip= id_n_chip # unpacked
      err=PixDimnType("chip",chip)
      if err.find("Error:") != -1:
          raise PixError(err)
      if not isinstance(id, int):
          raise PixError("Error: need 'int' type for chip-id not the type of '"+str(type(id))+"'." )

    
      chip = (c_double * 2)(chip[0], chip[1])
      out  = (c_double * 2)(0.0, 0.0)
 
      self._stat=_libc.pix_chip_to_tdet(id, chip, out)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError())
      self._mydict['chip'] = id,(chip[0],chip[1])
      self.isokay=True
      return out[0],out[1]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return  None,None           # return NONE for failure 

 
  def tdet2chip(self, tdet):
    """
    Transfer 2-D tdet to Chip coords.
    
    Arguments: 
              tdet   - 2-D TDET coords
    Return: (chip_id, (chip_x,chip_y)) in tuple
            where chip_id is in integer and chip coords in float.

    """

    try:
        err=PixDimnType("tdet",tdet)
        if err.find("Error:") != -1:
            raise PixError(err)
   
        tdet = (c_double * 2)(tdet[0], tdet[1])
        id   = (c_int * 1)(0)
        out  = (c_double * 2)(0.0, 0.0)

        self._stat=_libc.pix_tdet_to_chip(tdet,id, out)
        if self._stat != 0:
            self._stat = 0
            raise PixError(PixGetError())
        self._mydict['tdet'] = tdet[0],tdet[1]
        self.isokay=True
        return id[0],(out[0],out[1])
    except PixError as e:
        self.isokay=e.value
        print(e, file=sys.stderr)   # throw exception
        return  -1,(None,None)      # return -1 and NONE for failure 

  def chip2lsi(self, id_n_chip):
    """
    Transfer 2-D Chip coords to 3-D LSI coords.
    
    Arguments:
              id_n_chip - chip ID in integer and 2-D chip coords in float
                          in (id, (chip_x,chip_y)) tuple or list            
    Return: (lsi_x,lsi_y,lsi_z) in float

    """

    try:
      # check the format (id,(chipx,chipy)) in size 2 
      if len( id_n_chip ) != 2 :
          raise PixError("Error: expected input syntax: '(id, (chipx,chipy))' .")
 
      id,chip= id_n_chip # unpacked
      err=PixDimnType("chip",chip)
      if err.find("Error:") != -1:
          raise PixError(err)
      if not isinstance(id, int):
          raise PixError("Error: need 'int' type for chip-id not the type of '"+str(type(id))+"'." )
    
      chip = (c_double * 2)(chip[0], chip[1])
      out  = (c_double * 3)(0.0, 0.0, 0.0)
      self._stat=_libc.pix_chip_to_lsi(id, chip, out)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError())
      self._mydict['chip'] = id,(chip[0],chip[1])
      self.isokay=True
      return out[0],out[1],out[2]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return  None,None,None      # return NONE for failure 
  
  #--------------------------------------------------
  def lsi2chip(self, lsi):
    """
    Transfer  3-D LSI to 2-D Chip coords.
    
    Arguments:
              lsi - 3-D LSI coords             
    Return: (chip_id,(chip_x,chip_y)) in tuple
            where chip_id is in integer and chip coords in float.

    """

    try:
      err=PixDimnType("lsi",lsi,3)
      if err.find("Error:") != -1:
          raise PixError(err)
    
      lsi  = (c_double * 3)(lsi[0], lsi[1], lsi[2])
      id   = (c_int * 1) (0)
      out  = (c_double * 2)(0.0, 0.0)
    
      self._stat=_libc.pix_lsi_to_chip(lsi, id, out)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError() +", at '"+str(lsi)+"'.")
      self._mydict['lsi'] = lsi[0],lsi[1],lsi[2]   
      self.isokay=True
      return id[0],(out[0],out[1])
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return -1,(None,None)       # return -1 and NONE for failure 

  
  #--------------------------------------------------
  def chip2gdp(self, id_n_chip):
    """
    Transfer 2-D Chip coords to 2-D GDP coords.
    
    Arguments:
              id_n_chip - chip ID in integer and 2-D chip coords in float
                          in (id, (chip_x,chip_y)) tuple or list
    Return: (gdp_x,gdp_y) in float

    """

    try:
      # check the format (id, (chipx,chipy)) in size 2 
      if len( id_n_chip ) != 2 :
          raise PixError("Error: expected input syntax: '(id, (chipx,chipy))' .")
 
      id,chip= id_n_chip # unpacked
      err=PixDimnType("chip",chip)
      if err.find("Error:") != -1:
          raise PixError(err)
      if not isinstance(id, int):
          raise PixError("Error: need 'int' type for chip-id not the type of '"+str(type(id))+"'." )

      inp  = (c_double * 2)(chip[0], chip[1])
      gac  = (c_double * 2)(0.0,0.0)
      out  = (c_double * 2)(0.0,0.0)
    
      self._stat=_libc.pix_chip_to_gac(id, inp, gac)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError() +", at '"+str(id_n_chip)+"'.")

      self._stat=_libc.pix_gac_to_gdp(gac, out)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError() +", at '"+str(id_n_chip)+"'.")
      self._mydict['chip'] = id,(chip[0],chip[1])
      self.isokay=True
      return out[0],out[1]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return (None,None)          # return NONE for failure 

  #--------------------------------------------------
  def fpc2mnc(self, fpc):
    """
    Transfer 2-D FPC to 3-D MNC coords.
    
    Arguments: 
              fpc   - 2-D FPC  coords in float
    Return: (mnc_x,mnc_y,mnc_z) in float

    """

    try:
      err=PixDimnType("fpc",fpc)
      if err.find("Error:") != -1:
          raise PixError(err)
    
      inp  = (c_double * 2)(fpc[0], fpc[1])
      out  = (c_double * 3)(0.0,0.0,0.0)
   
      self._stat=_libc.pix_fpc_to_mnc(inp, out)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError() +", at '"+str(fpc)+"'.")
      self._mydict['fpc'] = fpc[0],fpc[1]
      self.isokay=True
      return out[0],out[1],out[2]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return (None,None, None)    # throw exception

  #--------------------------------------------------
  def mnc2fpc(self, mnc):
    """
    Transfer 3-D MNC to 2-D FPC coords.
    
    Arguments: 
              mnc   - 3-D MNC  coords in float
    Return: (fpc_x,fpc_y) in float

    """

    try:
      err=PixDimnType("mnc",mnc,3)
      if err.find("Error:") != -1:
          raise PixError(err)
    
      inp  = (c_double * 3)(mnc[0], mnc[1], mnc[2])
      out  = (c_double * 2)(0.0,0.0)
   
      self._stat=_libc.pix_mnc_to_fpc(inp, out)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError() +", at '"+str(mnc)+"'.")
      self._mydict['mnc'] = mnc[0],mnc[1],mnc[2]
      self.isokay=True
      return out[0],out[1]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return (None,None)          # return NONE for failure 
  
  #--------------------------------------------------
  def fpc2gdp(self, fpc):
    """
    Transfer 2-D fpc to 2-D gdp coords.
    
    Arguments: 
              fpc   - 2-D FPC coords in float
    Return: (gdp_x,gdp_y) in float

    """

    try:
      err=PixDimnType("fpc",fpc)
      if err.find("Error:") != -1:
          raise PixError(err)
     
      inp  = (c_double * 2)(fpc[0], fpc[1])
      out  = (c_double * 2)(0.0,0.0)
   
      self._stat=_libc.pix_fpc_to_gdp(inp, out)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError() +", at '"+str(fpc)+"'.")
      self._mydict['fpc'] = fpc[0],fpc[1]
      self.isokay=True
      return out[0],out[1]
    except PixError as e:
     self.isokay=e.value
     print(e, file=sys.stderr)   # throw exception
     return  (None,None)         # return NONE for failure 


  
  #--------------------------------------------------
  def gac2gdp(self, gac):
    """
    Transfer 2-D gac to 2-D gdp coords.
    
    Arguments: 
              gac   - 2-D GAC coords in float
    Return: (gdp_x,gdp_y) in float

    """

    try:
      err=PixDimnType("gac",gac)
      if err.find("Error:") != -1:
          raise PixError(err)
 
      inp  = (c_double * 2)(gac[0], gac[1])
      out  = (c_double * 2)(0.0,0.0)
   
      self._stat=_libc.pix_gac_to_gdp(inp, out)
      if self._stat != 0:
          self._stat = 0
          raise PixError(PixGetError() +", at '"+str(gac)+"'.")

      #self._mydict['gdp']= out[0],out[1]
      self._mydict['gac']= inp[0],inp[1]
      self.isokay=True
      return out[0],out[1]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return  (None,None)         # return NONE for failure 


  def gdp2gac(self, gdp):
    """
    Transfer 2-D gdp to 2-D gac coords.
    
    Arguments: 
              gdp   - 2-D GDP  coords in float
    Return: (gac_x,gac_y) in float

    """

    try:
      err=PixDimnType("gdp",gdp)
      if err.find("Error:") != -1:
          raise PixError(err)
    
      inp  = (c_double * 2)(gdp[0], gdp[1])
      out  = (c_double * 2)(0.0,0.0)
 
      self._stat=_libc.pix_gdp_to_gac(inp, out)
      if self._stat != 0:
          self._stat = 0
          raise PixError(PixGetError()+", at '"+str(gdp)+"'.")

      self._mydict['gdp']= gdp[0],gdp[1]
      self.isokay=True
      return out[0],out[1]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return (None,None)

  #--------------------------------------------------
  def app_aspect(self, fpc, aspoffs=None):
    """
    Transfer 2-D Detector to Sky coords with applying aspect solution.
    
    Arguments: 
              fpc      - 2-D FPC coords in float
              aspoffs  - 3-D aspect offsets in float
                         If aspoffs is omitted, the currently stored ones
                         will be used.
    Return: (sky_x,sky_y) in float

    """
    
    try:
      err=PixDimnType("fpc",fpc)
      if err.find("Error:") != -1:
          raise PixError(err)

      if aspoffs  == None:
          if 'aspoffs' in self._mydict:
              aspoffs=self._mydict['aspoffs']
      err=PixDimnType("asp-offsets",aspoffs,3)
      if err.find("Error:") != -1:
          raise PixError(err)
  
      inp  = (c_double * 2)(fpc[0], fpc[1])
      offs = (c_double * 3)(aspoffs[0],aspoffs[1],aspoffs[2])
      out  = (c_double * 2)(0.0,0.0)

      self._stat=_libc.pix_apply_aspect(inp, offs, out)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError() +", at '"+str(aspoffs)+"'.")

      self._mydict['aspoffs']=offs[0],offs[1],offs[2]
      self._mydict['fpc']=fpc[0],fpc[1]    
      self.isokay=True
      return out[0],out[1]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return  (None,None)         # return NONE for failure 

  def deapp_aspect(self, sky, aspoffs=None):
    """
    Transfer 2-D Sky to Detector coords with removing aspect solution.
    
    Arguments: 
              sky      - 2-D SKY coords in float
              aspoffs  - 3-D aspect offsets in float
                         If aspoffs is omitted, the currently stored ones
                         will be used.               
    Return: (fpc_x,fpc_y) in float

    """

    try:
        
      err=PixDimnType("sky",sky)
      if err.find("Error:") != -1:
          raise PixError(err)
   
      if aspoffs  == None:
          if 'aspoffs' in self._mydict:
              aspoffs=self._mydict['aspoffs']

      err=PixDimnType("asp-offsets",aspoffs,3)
      if err.find("Error:") != -1:
          raise PixError(err)

      inp  = (c_double * 2)(sky[0], sky[1])
      offs = (c_double * 3)(aspoffs[0],aspoffs[1],aspoffs[2])
      out  = (c_double * 2)(0.0,0.0)
   
    
      self._stat=_libc.pix_deapply_aspect(inp, offs, out)
      if self._stat != 0:
        self._stat = 0
        raise PixError(PixGetError() +", at '"+str(aspoffs)+"'.")
      self._mydict['aspoffs']=offs[0],offs[1],offs[2]
      self._mydict['sky']=sky[0],sky[1]
      self.isokay=True
      return out[0],out[1]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return (None,None)          # return NONE for failure 
     
   

  #--------------------------------------------------

  def grt_energy(self, gac=None):
    """
    Get photon energy from the given grating diffracted angles
    (dispersion and cross-dispersion) coordinates (GAC)[degree]
    at the current non-zero grating order.
    
    Arguments:
              gac - 2-D GAC [degree] in float
                    If gac is omitted, the currently stored gac
                    will be used.
    Return: photo energy [keV] in float
    
    """
    
    gac_input=gac
    if gac == None:
        if 'gac' in self._mydict:
            gac=self._mydict['gac']

    try:
        
      if gac==None:
          raise PixError("Error: need gac input before returning energy.")
      err=PixDimnType("gac",gac)
      if err.find("Error:") != -1:
          raise PixError(err)
    
      gac = (c_double *2 )(gac[0], gac[1])
      out = (c_double *1 )(0.0)
 
      self._stat =_libc.pix_get_energy(gac, out)
      if self._stat != 0 :
        self._stat = 0
        raise PixError(PixGetError() +", at '"+str(gac)+"'.")
      
      if gac_input != None:
        self._mydict['gac']=gac[0], gac[1]
      self.isokay=True
      return out[0]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return  None                # return NONE for failure 

  def grt_wavelen(self,  gac=None):
    """
    Get grating wavelength for grating-order larger than zero.

    Arguments:
              gac  - 2-D grating diffracted angular coordinates (GAC)
                     [degree] in float
                     If gac is omitted, the currently stored gac will be
                     used.
    Return: wave-length [Angstrom] in float
    
    """
    
    gac_input=gac
    if gac == None:
        if 'gac' in self._mydict:
            gac=self._mydict['gac']
        
    try:
        
      if gac==None:
          raise PixError("Error: need gac input before returning wave-length.")
      err=PixDimnType("gac", gac)
      if err.find("Error:") != -1:
          raise PixError(err)
    
      gac = (c_double *2 )(gac[0], gac[1])
      out = (c_double *1 )(0.0)
    
      self._stat =_libc.pix_get_grating_wavelength(gac, out)
      if  self._stat != 0:
        self._stat = 0 
        raise PixError(PixGetError() +", at '"+str(gac)+"'.")
        
      if gac_input != None:
        self._mydict['gac']=gac[0], gac[1]
      self.isokay=True
      return out[0]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return None                 # return NONE for failure 

  def fpc2msc(self, fpc):
    """
    Transfer 2-D FPC to 3-D MSC coordinates.
    
    Arguments:
               fpc - 2-D FPC coords in float
    Return:    (msc_x,msc_y,msc_z) in float
    
    """
    try:
        
      err=PixDimnType("fpc",fpc)
      if err.find("Error:") != -1:
          raise PixError(err)
    
      inp = (c_double *2 )(fpc[0], fpc[1])
      out = (c_double *3)(0.0,0.0,0.0)
      mnc = (c_double *3)(0.0,0.0,0.0)
  
      self._stat =_libc.pix_fpc_to_mnc(inp, mnc)
      if self._stat != 0 :
        raise PixError(PixGetError() +", at '"+fpc+"'.")
      
      self._stat =_libc.pix_mnc_to_msc(mnc, out)
      if self._stat != 0 :
          self._stat = 0
          raise PixError(PixGetError() +", at '"+str(fpc)+"'.")
      self._mydict['fpc']=fpc[0],fpc[1]
      self.isokay=True
      return out[0],out[1],out[2]
    except PixError as e:
      self.isokay=e.value
      print(e, file=sys.stderr)   # throw exception
      return (None,None,None)     # return NONE for failure 
        

  def msc2fpc(self, msc):
    """
    Transfer 3-D MSC to 2-D FPC coordinates.
    
    Arguments:
               msc - 3-D MSC coords in float
    Return:    (fpc_x,fpc_y) in float
    
    """
    try:
        
      err=PixDimnType("msc",msc,3)
      if err.find("Error:") != -1:
          raise PixError(err)
    
      inp = (c_double *3 )(msc[0], msc[1],msc[2])
      out = (c_double *2)(0.0,0.0)
      mnc = (c_double *3)(0.0,0.0)
    
      self._stat =_libc.pix_msc_to_mnc(inp, mnc)
      if self._stat != 0 :
        self._stat = 0 
        raise PixError(PixGetError() +", at '"+str(msc)+"'.")
      
      self._stat =_libc.pix_mnc_to_fpc(mnc, out)
      if self._stat != 0 :
        self._stat = 0 
        raise PixError(PixGetError() +", at '"+str(msc)+"'.")
      self._mydict['msc']=msc[0],msc[1],msc[2]
      self.isokay=True
      return out[0],out[1]
    
    except PixError as e:
      self.isokay=e.value
      print(e,  file=sys.stderr)   # throw exception
      return (None,None)           # return NONE for failure 

  def fpc2fc(self, fpc):
    """
    Transfer 2-D fpc to 3-D fc coords.
    
    Arguments: 
              fpc   - 2-D FPC coords in float
    Return:   (fc_x,fc_y, fc_z) in float

    """

    try:
        err=PixDimnType("fpc",fpc)
        if err.find("Error:") != -1:
            raise PixError(err)

        inp = (c_double * 2)(fpc[0], fpc[1])
        out = (c_double * 3)(0.0,0.0,0.0)
           
        self._stat =_libc.pix_fpc_to_fc(inp, out)
        if self._stat != 0 :
            self._stat = 0 
            raise PixError(PixGetError() +", at '"+str(fpc)+"'.")
        self._mydict['fpc']=fpc[0],fpc[1]
        self.isokay=True
    except PixError as e:
        self.isokay=e.value
        print(e,  file=sys.stderr)   # throw exception
        return (None,None,None)      # return NONE for failure 

 
    # successed and return outputs
    return out[0],out[1],out[2]


  def fc2fpc(self, fc):
    """
    Transfer 3-D fc to 2-D fpc coords.
    
    Arguments: 
              fc   - 3-D FC coords in float
    Return: (fpc_x,fpc_y) tube in float

    """

    try:
        err=PixDimnType("fc",fc, 3)
        if err.find("Error:") != -1:
            raise PixError(err)
            
        inp  = (c_double * 3)(fc[0], fc[1], fc[2])
        out  = (c_double * 2)(0.0,0.0)

        self._stat =_libc.pix_fc_to_fpc(inp, out)
        if self._stat != 0 :
            self._stat = 0 
            raise PixError(PixGetError() +", at '"+str(fc)+"'.")
        self._mydict['fc']=fc[0],fc[1],fc[2]
        self.isokay=True
    except PixError as e:
        self.isokay=e.value
        print(e,  file=sys.stderr)   # throw exception
        return (None,None)           # return NONE for failure 


 
    # successed and return outputs
    return out[0],out[1]


  def gzo2fc(self, gzo):
    """
    Transfer 3-D gzo to 3-D fc coords.
    
    Arguments: 
             gzo   - 3-D GZO vector in mm in float
    Return: (fc_x,fc_y,fc_z) in mm in float


    Note: 
        gzo must be a vector in mm, as the conversion, pix_gzo_to_fc, is to take 
        'gzo2fc_transf' matrix  which requires input gzo in mm.

    """

    try:
        err=PixDimnType("gzo", gzo, 3)
        if err.find("Error:") != -1:
            raise PixError(err)
            
        inp  = (c_double * 3)(gzo[0], gzo[1], gzo[2])
        out  = (c_double * 3)(0.0,0.0,0.0)

        self._stat =_libc.pix_gzo_to_fc(inp, out)
        if self._stat != 0 :
            self._stat = 0 
            raise PixError(PixGetError() +", at '"+str(gzo)+"'.")
        self._mydict['gzo']=gzo[0],gzo[1],gzo[2]
        self.isokay=True
    except PixError as e:
        self.isokay=e.value
        print(e,  file=sys.stderr)   # throw exception
        return (None,None,None)      # return NONE for failure 

 
    # successed and return outputs
    return out[0],out[1],out[2]

  def fc2gzo(self, fc):
    """
    Transfer 3-D fc coords to 3-D gzo position vector
      
    Arguments: 
                fc   - 3-D FC coords in mm in float
    Return:   (gzo[0],gzo[1],gzo[2]) tube in mm in float
      

    Note: 
        fc must be a vector in mm, as the conversion, pix_fc_to_gzo, is to take 
        'fc2gzo_transf' matrix which requires input fc in mm.

    """

    try:
        err=PixDimnType("fc",fc, 3)
        if err.find("Error:") != -1:
            raise PixError(err)
            
        inp  = (c_double * 3)(fc[0], fc[1], fc[2])
        out  = (c_double * 3)(0.0,0.0,0.0)


        self._stat =_libc.pix_fc_to_gzo(inp, out)
        if self._stat != 0 :
            self._stat = 0 
            raise PixError(PixGetError() +", at '"+str(fc)+"'.")
        self._mydict['fc']=fc[0],fc[1],fc[2]
        self.isokay=True
    except PixError as e:
        self.isokay=e.value
        print(e,  file=sys.stderr)   # throw exception
        return (None,None,None)      # return NONE for failure 
 
    # successed and return outputs
    return out[0],out[1],out[2]


  def gac2gzo(self,gac):
    """
    Transfer 2-D gac coords to 3-D gzo vector.
    
    Arguments: 
              gac   - 2-D GAC angular vector in float
    Return:   (gzo_x,gzo_y,gzo_z) in float
    

    Note: 
        gzo, converted from gac, is a unit vector in direction. As it does not hold the original physical
        quantities (in mm), the reverse conversion may be meaningless. In other words, if one executes the 
        reverse sequence, gzo2fc, fc2fpc, fpc2chip, the resulting chip (and chip-id) will not match the 
        original coordinates from the forward sequence, chip2fpc, fpc2fc, fc2gac.


    """

    try:
        err=PixDimnType("gac",gac)
        if err.find("Error:") != -1:
            raise PixError(err)

        inp  = (c_double * 2)(gac[0], gac[1])
        out  = (c_double * 3)(0.0,0.0,0.0)

        self._stat =_libc.pix_gac_to_gzo(inp, out)
        if self._stat != 0 :
            self._stat = 0 
            raise PixError(PixGetError() +", at '"+str(gac)+"'.")
        self._mydict['gac']=gac[0],gac[1]
        self.isokay=True
    except PixError as e:
        self.isokay=e.value
        print(e,  file=sys.stderr)   # throw exception
        return (None,None,None)      # return NONE for failure 
 
 
    # successed and return outputs
    return out[0],out[1],out[2]


  def gzo2gac(self,gzo):
    """
    Transfer 3-D gzo to 2-D gac angular vector.
    
    Arguments: 
               gzo   - 3-D GZO vector in float
    Return:   (gac_x,gac_y) in float

 
   Note: 
        gzo, the input, can be either physical or direction vector. 
        But the gzo, if returned from gac2gzo conversion, is definitely a direction vector.

 
    """

    try:
        err=PixDimnType("gzo",gzo, 3)
        if err.find("Error:") != -1:
            raise PixError(err)

        inp  = (c_double * 3)(gzo[0], gzo[1], gzo[2])
        out  = (c_double * 2)(0.0,0.0)


        self._stat =_libc.pix_gzo_to_gac(inp, out)
        if self._stat != 0 :
            self._stat = 0 
            raise PixError(PixGetError() +", at '"+str(gzo)+"'.")
        self._mydict['gzo']=gzo[0],gzo[1],gzo[2]
        self.isokay=True
    except PixError as e:
        self.isokay=e.value
        print(e,  file=sys.stderr)   # throw exception
        return (None,None)           # return NONE for failure 
 
 
    # successed and return outputs
    return out[0],out[1]

  
  def curstat(self):
    """ return the current Class status with concealing the caldb path.
    command "p" is similar to 
            "print(p.curstat())" but the real-path vs '<path>'
    """
    
    attrLst = self.__class__._confLst
    
    classinfo = "\n\t%s\
    \n\tThe '%s' configuration:" % (50*'=',self.__class__.__name__)
    for key in attrLst:
        if key.find("CALDB") != -1:  # conceal the specific path for regression tests
            classinfo = classinfo+"\n\t%s: '%s'" % (key.rjust(10), "<path>")
        else:
            classinfo = classinfo+"\n\t%s: '%s'" % (key.rjust(10), self.__dict__[key])
    classinfo = classinfo+"\n\t%s\n" %(50*"=")    

    config   =classinfo
    default  = self._lstdefs()
    curinput = self._lstattrs()
    return(config + default +curinput)
       

def pixhelp():
  """
  Show how to get help for Pixlib python module application.
  """
  
  print("""
  ---------------------------------
  Help for info of pixlib.py package
  
  Help for document of Pixlib module class
  >>> help(Pixlib)
  
  The Pixlib Usage:
  >>> usage()
  
    """)
def usage():
  """
  Show the usage with example for Pixlib python module.
  """
  
  print("""
  -------------------------- 
  Examples of Pixlib Usage:
  --------------------------
  
  % Python
  >>> from pixlib import * 
  >>> p = Pixlib("chandra")
  >>> p

        ==================================================    
        The 'Pixlib' configuration:
         telescope: 'chandra'
              geom: 'geom.par'
             CALDB: '/data/regression_test/development/CALDB4/'
        ==================================================

        ***** The current defaults *****
          detector = 'ACIS'
          aimpoint = 'AI1'
           tdetsys = 'ACIS-2.2'
             fpsys = 'FP-1.1'
            gdpsys = 'ASC-GDP-1.1'
           grating = 'HEG'
  
  >>> id_chip=3,(783.8,894.0)
  >>> tdet=p.chip2tdet(id_chip)
  >>> tdet
  (4238.0000004819722, 3844.8000005497797)
  >>> p.tdet2chip(tdet)
  (3, (783.79999999999995, 894.00000000000034))
  
  >>> fpc=p.chip2fpc(id_chip)
  >>> p.fpc2chip(fpc)
  (3, (783.7899414898169, 893.99118205816808))

  >>> p.detecor=3
  >>> p.isokay
  0
  >>> p
  Error: need 'str' type for detector not the type of '<type 'int'>'.

  >>> p.detecor='hrc'
  >>> p.isokay
  0
  >>> p
  Error: failed to set detector, at 'hrc'.

  >>> p.detector='hrc-i'
  >>> p.isokay
  1
  >>> p
 
        ==================================================
        The 'Pixlib' configuration:
         telescope: 'chandra'
              geom: 'geom.par'
             CALDB: '/data/regression_test/development/CALDB4/'
        ==================================================

        ***** The current defaults *****
          aimpoint = 'HI1'
           chipsys = 'AXAF-HRC-1.1'
           tdetsys = 'HRC-2.4I'
             fpsys = 'FP-2.1'
            gdpsys = 'ASC-GDP-1.1'
           grating = 'HEG'

        ***** The current inputs *****
          detector = 'hrc-i'
 
  >>> p.close
  >>>

  """)


if __name__ == "__main__":
  print("Tests of the module are to be done in pixtest.py module")
  p=Pixlib("chandra")
  print(p)

  p.detector=23
  print(p)

  p.detector="hrc-I"
  print(p)
  
  p.detector="acis"
  print(p)
  id=3
  chip=783.8,894.0
  tdet=p.chip2tdet(id, chip)
  print("chip->tdet=",tdet,p)
  p.tdet2chip(tdet)
  p.aimpoint="ai1"
  fpc=p.chip2fpc(id,chip)
  print("chip->fpc=",fpc,p)
  chip=p.fpc2chip(fpc)
  print("fpc->chip=",chip,p)
  p.close
  
