00001
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
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)
00120
00121
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
00299 shape_child = xch.shape
00300 len_child = len(self.list_of_children)
00301 geo_shape = np.hstack(([len_child], xch.shape))
00302
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
00327 shape_child = ach.shape
00328 len_child = len(self.list_of_children)
00329 geo_shape = np.hstack(([len_child], ach.shape))
00330
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
00359 shape_child = car.shape
00360 len_child = len(self.list_of_children)
00361 geo_shape = np.hstack(([len_child], car.shape))
00362
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
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
00431 if self.algo is not None :
00432
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
00450 if arr.size == 143560 and self.oname == 'CSPAD2X2:V1' :
00451
00452 return two2x1ToData2x2(arr)
00453 return arr
00454
00455
00456
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