PSCalib/src/GeometryObject.py

Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #------------------------------
00003 """:py:class:`PSCalib.GeometryObject` - building block for hierarchical geometry
00004 
00005 Usage::
00006 
00007     # Methods of this class are used internally in PSCalib.GeometryAccess
00008     # and are not supposed to be used directly...
00009 
00010     from PSCalib.GeometryObject import GeometryObject
00011 
00012     # Instatiation of the geometry object
00013     # d = <dictionary-of-input-parameters>
00014     geo = GeometryObject(**d)
00015 
00016     # test print methods:
00017     geo.print_geo()
00018     geo.print_geo_children()
00019 
00020     # modification methods:
00021     geo.set_geo_pars(self, x0, y0, z0, rot_z, rot_y, rot_x, tilt_z, tilt_y, tilt_x)
00022     geo.move_geo(dx, dy, dz)
00023     geo.tilt_geo(dt_x, dt_y, dt_z)
00024 
00025     # access methods:
00026     s      = geo.str_data()
00027     parent = geo.get_parent()
00028     list_of_children = geo.get_list_of_children()
00029     oname  = geo.get_geo_name()
00030     oindex = geo.get_geo_index()
00031     pname  = geo.get_parent_name()
00032     pindex = geo.get_parent_index()
00033     X,Y,Z  = geo.get_pixel_coords(do_tilt=true)
00034     X,Y    = geo.get_2d_pixel_coords(do_tilt=true)
00035     area   = geo.get_pixel_area()
00036     #mbits = +1-edges, +2-wide pixels, +4-non-bonded pixels, +8/+16 - four/eight neighbours of non-bonded
00037     mask   = geo.get_pixel_mask(mbits=0377)
00038     npixels= geo.get_size_geo_array()
00039     pixsize= geo.get_pixel_scale_size()
00040     x0, y0, z0             = geo.get_origin()
00041     rot_z, rot_y, rot_x    = geo.get_rot()
00042     tilt_z, tilt_y, tilt_x = geo.get_tilt()
00043     
00044     # private methods for internal consumption:
00045     geo.set_parent(parent)
00046     geo.add_child(child)
00047     Xt, Yt, Zt = geo.transform_geo_coord_arrays(X, Y, Z, do_tilt=True)
00048     Xt, Yt     = geo.transform_2d_geo_coord_arrays(X, Y, do_tilt=True)
00049 
00050     # global methods:
00051     Xrot, Yrot = rotation_cs(X, Y, C, S)
00052     Xrot, Yrot = rotation(X, Y, angle_deg)
00053 
00054     # global methods only for CSPAD2x2 array conversion between (2,185,388) and (185,388,2):
00055     arrTwo2x1 = data2x2ToTwo2x1(asData2x2)
00056     asData2x2 = two2x1ToData2x2(arrTwo2x1)
00057 
00058 @see :py:class:`PSCalib.GeometryAccess`
00059 
00060 For more detail see `Detector Geometry <https://confluence.slac.stanford.edu/display/PSDM/Detector+Geometry>`_.
00061 
00062 This software was developed for the SIT project.  If you use all or 
00063 part of it, please give an appropriate acknowledgment.
00064 
00065 $Revision: 11946 $
00066 
00067 @version $Id: GeometryObject.py 11946 2016-05-19 00:29:37Z dubrovin@SLAC.STANFORD.EDU $
00068 
00069 @author Mikhail S. Dubrovin
00070 """
00071 #--------------------------------
00072 __version__ = "$Revision: 11946 $"
00073 #--------------------------------
00074 
00075 import os
00076 import sys
00077 import math
00078 import numpy as np
00079 
00080 #from PyCSPadImage.PixCoords2x1 import cspad2x1_one
00081 from PSCalib.SegGeometryStore import sgs
00082 
00083 #------------------------------
00084 
00085 def rotation_cs(X, Y, C, S) :
00086     """For numpy arrays X and Y returns the numpy arrays of Xrot and Yrot
00087     """
00088     Xrot = X*C - Y*S 
00089     Yrot = Y*C + X*S 
00090     return Xrot, Yrot
00091 
00092 #------------------------------
00093 
00094 def rotation(X, Y, angle_deg) :
00095     """For numpy arrays X and Y returns the numpy arrays of Xrot and Yrot rotated by angle_deg
00096     """
00097     angle_rad = math.radians(angle_deg)
00098     S, C = math.sin(angle_rad), math.cos(angle_rad)
00099     return rotation_cs(X, Y, C, S)
00100 
00101 #------------------------------
00102 
00103 class GeometryObject :
00104 
00105     def __init__(self, pname=None, pindex=None, \
00106                  oname=None, oindex=None, \
00107                  x0=0, y0=0, z0=0, \
00108                  rot_z=0, rot_y=0, rot_x=0, \
00109                  tilt_z=0, tilt_y=0, tilt_x=0) : 
00110 
00111         self.pname  = pname
00112         self.pindex = pindex
00113 
00114         self.oname  = oname
00115         self.oindex = oindex
00116 
00117         self.set_geo_pars(x0, y0, z0, rot_z, rot_y, rot_x, tilt_z, tilt_y, tilt_x)
00118 
00119         self.algo = sgs.Create(self.oname, pbits=0) # ex.: SegGeometryCspad2x1V1(...)
00120 
00121         # ---- 2-nd stage
00122         self.parent = None
00123         self.list_of_children = []
00124         
00125 #------------------------------
00126         
00127     def set_geo_pars(self, \
00128                      x0=0, y0=0, z0=0, \
00129                      rot_z=0, rot_y=0, rot_x=0, \
00130                      tilt_z=0, tilt_y=0, tilt_x=0) : 
00131         """ Sets self object geometry parameters
00132         """
00133         self.x0 = x0
00134         self.y0 = y0
00135         self.z0 = z0
00136 
00137         self.rot_z  = rot_z  
00138         self.rot_y  = rot_y 
00139         self.rot_x  = rot_x 
00140                             
00141         self.tilt_z = tilt_z
00142         self.tilt_y = tilt_y
00143         self.tilt_x = tilt_x
00144 
00145 #------------------------------
00146         
00147     def move_geo(self, dx=0, dy=0, dz=0) :
00148         """ Adds offset for origin of the self object w.r.t. current position
00149         """
00150         self.x0 += dx
00151         self.y0 += dy
00152         self.z0 += dz
00153 
00154 #------------------------------
00155         
00156     def tilt_geo(self, dt_x=0, dt_y=0, dt_z=0) :
00157         """ Tilts the self object w.r.t. current orientation
00158         """
00159         self.tilt_z += dt_z
00160         self.tilt_y += dt_y
00161         self.tilt_x += dt_x
00162 
00163 #------------------------------
00164 
00165     def print_geo(self) :
00166         """ Print info about self geometry object
00167         """
00168         print 'parent:%10s %2d   geo: %10s %2d' % (self.pname, self.pindex, self.oname, self.oindex) + \
00169               '  x0:%8.0f  y0:%8.0f  z0:%8.0f' % (self.x0, self.y0, self.z0) + \
00170               '  rot_z:%8.3f  rot_y:%8.3f  rot_x:%8.3f' % (self.rot_z, self.rot_y, self.rot_x) + \
00171               '  tilt_z:%8.5f  tilt_y:%8.5f  tilt_x:%8.5f' % (self.tilt_z, self.tilt_y, self.tilt_x)
00172 
00173 #------------------------------
00174 
00175     def str_data(self) :
00176         """ Returns a string of data to save in file
00177         """
00178         s_rot_x = ('%8.3f' % self.rot_x).rstrip('0').rstrip('.')
00179         s_rot_y = ('%8.3f' % self.rot_y).rstrip('0').rstrip('.')
00180         s_rot_z = ('%8.3f' % self.rot_z).rstrip('0').rstrip('.')
00181         return '%s %3d %s %3d' % (self.pname.ljust(11), self.pindex, self.oname.ljust(11), self.oindex) + \
00182                '  %8.0f %8.0f %8.0f' % (self.x0, self.y0, self.z0) + \
00183                '  %8s   %8s   %8s  ' % (s_rot_z, s_rot_y, s_rot_x) + \
00184                '  %8.5f %8.5f %8.5f' % (self.tilt_z, self.tilt_y, self.tilt_x)
00185 
00186 #------------------------------
00187 
00188     def print_geo_children(self) :
00189         """ Print info about children of self geometry object
00190         """
00191         msg = 'parent:%10s %2d   geo: %10s %2d #children: %d:' % \
00192               (self.pname, self.pindex, self.oname, self.oindex, len(self.list_of_children))
00193         for geo in self.list_of_children :
00194             msg += ' %s:%d' % (geo.oname, geo.oindex)
00195         print msg
00196 
00197 #------------------------------
00198 
00199     def set_parent(self, parent) :
00200         """ Set parent geometry object for self
00201         """
00202         self.parent = parent
00203 
00204 #------------------------------
00205 
00206     def add_child(self, child) :
00207         """ Add children geometry object to the list
00208         """
00209         self.list_of_children.append(child)
00210 
00211 #------------------------------
00212 
00213     def get_parent(self) :
00214         """ Returns parent geometry object
00215         """
00216         return self.parent
00217 
00218 #------------------------------
00219 
00220     def get_list_of_children(self) :
00221         """ Returns list of children geometry objects
00222         """
00223         return self.list_of_children
00224 
00225 #------------------------------
00226 
00227     def get_geo_name(self) :
00228         """ Returns self geometry object name
00229         """
00230         return self.oname
00231 
00232 #------------------------------
00233 
00234     def get_geo_index(self) :
00235         """ Returns self geometry object index
00236         """
00237         return self.oindex
00238 
00239 #------------------------------
00240 
00241     def get_parent_name(self) :
00242         """ Returns parent geometry object name
00243         """
00244         return self.pname
00245 
00246 #------------------------------
00247 
00248     def get_parent_index(self) :
00249         """ Returns parent geometry object index
00250         """
00251         return self.pindex
00252 
00253 #------------------------------
00254 
00255     def transform_geo_coord_arrays(self, X, Y, Z, do_tilt=True) :
00256         """ Transform geometry object coordinates to the parent frame
00257         """
00258         angle_z = self.rot_z + self.tilt_z if do_tilt else self.rot_z
00259         angle_y = self.rot_y + self.tilt_y if do_tilt else self.rot_y
00260         angle_x = self.rot_x + self.tilt_x if do_tilt else self.rot_x
00261 
00262         X1, Y1 = rotation(X,  Y,  angle_z)
00263         Z2, X2 = rotation(Z,  X1, angle_y)
00264         Y3, Z3 = rotation(Y1, Z2, angle_x)
00265 
00266         Zt = Z3 + self.z0
00267         Yt = Y3 + self.y0
00268         Xt = X2 + self.x0
00269 
00270         return Xt, Yt, Zt 
00271 
00272 #------------------------------
00273 
00274     def get_pixel_coords(self, do_tilt=True) :
00275         """ Returns three numpy arrays with pixel X, Y, Z coordinates for self geometry object
00276         """
00277         if self.algo is not None :
00278             xac, yac, zac = self.algo.pixel_coord_array()
00279             return self.transform_geo_coord_arrays(xac, yac, zac, do_tilt)
00280 
00281         xac, yac, zac = None, None, None
00282         for ind, child in enumerate(self.list_of_children) :
00283             if child.oindex != ind :
00284                 print 'WARNING! Geometry object %s:%d has non-consequtive index in calibration file, reconst index:%d' % \
00285                       (child.oname, child.oindex, ind)
00286 
00287             xch, ych, zch = child.get_pixel_coords(do_tilt)
00288 
00289             if ind==0 :
00290                 xac = xch
00291                 yac = ych
00292                 zac = zch
00293             else :
00294                 xac = np.vstack((xac, xch))
00295                 yac = np.vstack((yac, ych))
00296                 zac = np.vstack((zac, zch))
00297 
00298         # define shape for output x,y,z arrays
00299         shape_child = xch.shape
00300         len_child = len(self.list_of_children)
00301         geo_shape = np.hstack(([len_child], xch.shape))
00302         #print 'geo_shape = ', geo_shape        
00303         xac.shape = geo_shape
00304         yac.shape = geo_shape
00305         zac.shape = geo_shape
00306         X, Y, Z = self.transform_geo_coord_arrays(xac, yac, zac, do_tilt)
00307         return self.det_shape(X), self.det_shape(Y), self.det_shape(Z) 
00308 
00309 #------------------------------
00310 
00311     def get_pixel_areas(self) :
00312         """ Returns numpy array with pixel areas for self geometry object
00313         """
00314         if self.algo is not None :
00315             return self.algo.pixel_area_array()
00316 
00317         aar = None
00318         for ind, child in enumerate(self.list_of_children) :
00319             if child.oindex != ind :
00320                 print 'WARNING! Geometry object %s:%d has non-consequtive index in calibration file, reconst index:%d' % \
00321                       (child.oname, child.oindex, ind)
00322 
00323             ach = child.get_pixel_areas()
00324             aar = ach if ind==0 else np.vstack((aar, ach))
00325 
00326         # define shape for output x,y,z arrays
00327         shape_child = ach.shape
00328         len_child = len(self.list_of_children)
00329         geo_shape = np.hstack(([len_child], ach.shape))
00330         #print 'geo_shape = ', geo_shape        
00331         aar.shape = geo_shape
00332         return self.det_shape(aar)
00333 
00334 #------------------------------
00335 
00336     def get_pixel_mask(self, mbits=0377) :
00337         """ Returns numpy array with pixel mask for self geometry object.
00338 
00339         mbits =+1 - mask edges
00340                +2 - two wide-pixel central columns
00341                +4 - non-bonded pixels
00342                +8 - nearest four neighbours of non-bonded pixels
00343                +16- eight neighbours of non-bonded pixels
00344         """
00345 
00346         if self.algo is not None :
00347             return self.algo.pixel_mask_array(mbits)
00348 
00349         oar = None
00350         for ind, child in enumerate(self.list_of_children) :
00351             if child.oindex != ind :
00352                 print 'WARNING! Geometry object %s:%d has non-consequtive index in calibration file, reconst index:%d' % \
00353                       (child.oname, child.oindex, ind)
00354 
00355             car = child.get_pixel_mask(mbits)
00356             oar = car if ind==0 else np.vstack((oar, car))
00357 
00358         # define shape for output x,y,z arrays
00359         shape_child = car.shape
00360         len_child = len(self.list_of_children)
00361         geo_shape = np.hstack(([len_child], car.shape))
00362         #print 'geo_shape = ', geo_shape        
00363         oar.shape = geo_shape
00364         return self.det_shape(oar)
00365 
00366 #------------------------------
00367 
00368     def get_size_geo_array(self) :
00369         """ Returns size of  self geometry object
00370         """
00371         if self.algo is not None : return self.algo.size()
00372 
00373         size_arr = 0
00374         for child in self.list_of_children :
00375             size_arr += child.get_size_geo_array()
00376 
00377         return size_arr
00378 
00379 #------------------------------
00380 
00381     def get_pixel_scale_size(self) :
00382         """ Returns pixel scale size of the geometry object from the first found segment
00383         """
00384         if self.algo is not None : return self.algo.pixel_scale_size()
00385 
00386         for child in self.list_of_children :
00387             return child.get_pixel_scale_size()
00388 
00389 #------------------------------
00390         
00391     def get_origin(self) :
00392         """ Returns object origin x, y, z coordinates [um] relative to parent frame
00393         """
00394         return  self.x0, self.y0, self.z0
00395 
00396 #------------------------------
00397         
00398     def get_rot(self) :
00399         """ Returns object tilt angles [degree] around z, y, and x axes, respectively
00400         """
00401         return self.rot_z, self.rot_y, self.rot_x
00402 
00403 #------------------------------
00404         
00405     def get_tilt(self) :
00406         """ Returns object rotation angles [degree] around z, y, and x axes, respectively
00407         """
00408         return self.tilt_z, self.tilt_y, self.tilt_x
00409 
00410 #------------------------------
00411 #------------------------------
00412 # Additional to interface 2-d methods
00413 #------------------------------
00414 #------------------------------
00415 
00416     def transform_2d_geo_coord_arrays(self, X, Y, do_tilt=True) :
00417         """ Simplified version of transform_geo_coord_arrays(...) for 2-d case
00418         """
00419         angle_z = self.rot_z + self.tilt_z if do_tilt else self.rot_z
00420         X1, Y1 = rotation(X,  Y,  angle_z)
00421         Xt = X1 + self.x0
00422         Yt = Y1 + self.y0
00423         return Xt, Yt
00424 
00425 #------------------------------
00426 
00427     def get_2d_pixel_coords(self, do_tilt=True) :
00428         """ Simplified version of get_pixel_coords() for 2-d case 
00429         """
00430         #if self.oname == 'SENS2X1:V1' : 
00431         if self.algo is not None :
00432             #xac, yac, zac = self.algo.get_xyz_maps_um()
00433             xac, yac, zac = self.algo.pixel_coord_array()
00434             return self.transform_2d_geo_coord_arrays(xac, yac, do_tilt)
00435 
00436         xac, yac = [], []
00437         for child in self.list_of_children :
00438             xch, ych = child.get_2d_pixel_coords(do_tilt)
00439             xac += list(xch.flatten())
00440             yac += list(ych.flatten())
00441         X, Y = self.transform_2d_geo_coord_arrays(np.array(xac), np.array(yac), do_tilt)
00442         return self.det_shape(X), self.det_shape(Y) 
00443 
00444 #------------------------------
00445 
00446     def det_shape(self, arr) :
00447         """ Check detector dependency and re-shape array if necessary
00448         """
00449         #print 'PSCalib.GeometryObject.det_shape(...):  arr.size: %d   self.oname: %s' % (arr.size, self.oname)
00450         if arr.size == 143560 and self.oname == 'CSPAD2X2:V1' : # Shuffle pixels once for 2*185*388 and CSPAD2X2:V1 ONLY:
00451             # shaffle array for cspad2x2
00452             return two2x1ToData2x2(arr)
00453         return arr
00454 
00455 #------------------------------
00456 #------ Global Method(s) ------
00457 #------------------------------
00458 
00459 def data2x2ToTwo2x1(arr2x2) :
00460     """Converts array shaped as CSPAD2x2 data (185,388,2)
00461     to two 2x1 arrays with shape=(2,185,388)
00462     """
00463     if arr2x2.size != 2*185*388 :
00464         raise ValueError('Expected n-d array size=185*388*2, input size=%d' % arr2x2.size)
00465 
00466     if arr2x2.shape[-1] != 2 :
00467         raise ValueError('Expected n-d array shape=(185,388,2), input shape=%s' % str(arr2x2.shape))
00468 
00469     arr2x2.shape = (185,388,2) 
00470     return np.array([arr2x2[:,:,0], arr2x2[:,:,1]])
00471 
00472 #------------------------------
00473 
00474 def two2x1ToData2x2(arrTwo2x1) :
00475     """Converts array shaped as two 2x1 arrays (2,185,388) or (2*185,388)
00476     to CSPAD2x2 data shape=(185,388,2)
00477     """
00478     if arrTwo2x1.size != 2*185*388 :
00479         raise ValueError('Expected n-d array size=2*185*388, input size=%d' % arrTwo2x1.size)
00480 
00481     if arrTwo2x1.shape[-1] != 388 :
00482         raise ValueError('Expected n-d array shape=(2,185,388), input shape=%s' % str(arrTwo2x1.shape))
00483 
00484     arrTwo2x1.shape = (2,185,388)
00485     arr2x2 = np.array(zip(arrTwo2x1[0].flatten(), arrTwo2x1[1].flatten()))
00486     arr2x2.shape = (185,388,2)
00487     return arr2x2
00488 
00489 #------------------------------
00490 #------------------------------
00491 #------------------------------
00492 
00493 if __name__ == "__main__" :
00494     print 78*'='+'\n==  Tests for this module are available in pyimgalgos/src/GeometryAccess.py ==\n'+78*'='
00495     sys.exit ('End of %s' % sys.argv[0])
00496 
00497 #------------------------------
00498 #------------------------------
00499 #------------------------------
00500 
00501 

Generated on 19 Dec 2016 for PSANAmodules by  doxygen 1.4.7