def geometry(self): geometry_string = self._fetch('geometry') geometry_access = None if geometry_string is not None: from psana.pscalib.geometry.GeometryAccess import GeometryAccess geometry_access = GeometryAccess() geometry_access.load_pars_from_str(geometry_string) return geometry_access
def _det_geo(self): """ """ if self._geo_ is None: geotxt, meta = self._det_geotxt_and_meta() if geotxt is None: logger.debug('_det_geo geotxt is None') return None self._geo_ = GeometryAccess() self._geo_.load_pars_from_str(geotxt) return self._geo_
def det_geo(self): """ """ if self.geo is None: geotxt, meta = self.det_geotxt_and_meta() if geotxt is None: logger.warning('det_geo geotxt is None') return None self.geo = GeometryAccess() self.geo.load_pars_from_str(geotxt) return self.geo
def geo(self): """ return GeometryAccess() object """ if self._geo is None: geotxt, meta = self.geotxt_and_meta() if geotxt is None: geotxt = self.geotxt_default() if ut.is_none(geotxt, 'geo geotxt is None'): return None self._geo = GeometryAccess() self._geo.load_pars_from_str(geotxt) return self._geo
def _det_geo(self): """ """ if self._geo_ is None: geotxt, meta = self._det_geotxt_and_meta() if geotxt is None: geotxt = self._det_geotxt_default() if is_none(geotxt, '_det_geo geotxt is None'): return None self._geo_ = GeometryAccess() self._geo_.load_pars_from_str(geotxt) return self._geo_
def data_geo(ntest): """Method for tests: returns test data numpy array and geometry object """ from time import time from psana.pscalib.calib.NDArrIO import save_txt, load_txt from psana.pscalib.geometry.GeometryAccess import GeometryAccess dir = '/reg/g/psdm/detector/alignment/cspad/calib-cxi-camera2-2016-02-05' #fname_nda = '%s/nda-water-ring-cxij4716-r0022-e000001-CxiDs2-0-Cspad-0-ave.txt' % dir #fname_nda = '%s/nda-water-ring-cxij4716-r0022-e014636-CxiDs2-0-Cspad-0-ave.txt' % dir #fname_nda = '%s/nda-lysozyme-cxi02416-r0010-e052421-CxiDs2-0-Cspad-0-max.txt' % dir fname_nda = '%s/nda-lysozyme-cxi01516-r0026-e093480-CxiDs2-0-Cspad-0-max.txt'%dir if ntest in (21,28,29,30)\ else '%s/nda-water-ring-cxij4716-r0022-e014636-CxiDs2-0-Cspad-0-ave.txt'%dir fname_geo = '%s/calib/CsPad::CalibV1/CxiDs2.0:Cspad.0/geometry/geo-cxi01516-2016-02-18-Ag-behenate-tuned.data' % dir #fname_geo = '%s/geo-cxi02416-r0010-2016-03-11.txt' % dir fname_gain = '%s/calib/CsPad::CalibV1/CxiDs2.0:Cspad.0/pixel_gain/cxi01516-r0016-2016-02-18-FeKalpha.data' % dir # load n-d array with averaged water ring arr = load_txt(fname_nda) #arr *= load_txt(fname_gain) #print_ndarr(arr,'water ring') arr.shape = (arr.size, ) # (32*185*388,) # retrieve geometry t0_sec = time() geo = GeometryAccess(fname_geo) geo.move_geo('CSPAD:V1', 0, 1600, 0, 0) geo.move_geo('QUAD:V1', 2, -100, 0, 0) #geo.get_geo('QUAD:V1', 3).print_geo() print('Time to load geometry %.3f sec from file\n%s' % (time() - t0_sec, fname_geo)) return arr, geo
def issue_2022_03_02(): """epix100 default geometry implementation """ from psana.pscalib.geometry.GeometryAccess import GeometryAccess from psana.detector.NDArrUtils import info_ndarr from psana import DataSource #ds = DataSource(exp='tmox49720',run=209) #orun = next(ds.runs()) #det = orun.Detector('epix100') ds = DataSource(exp='rixx45619',run=119) orun = next(ds.runs()) det = orun.Detector('epixhr') for nevt,evt in enumerate(orun.events()): geotxt = det.raw._det_geotxt_default() print('_det_geotxt_default:\n%s' % geotxt) o = GeometryAccess() o.load_pars_from_str(geotxt) x,y,z = o.get_pixel_coords() print(info_ndarr(x,'x:')) if det.raw.image(evt) is not None: break
class AreaDetector(DetectorImpl): def __init__(self, *args, **kwargs): logger.debug('AreaDetector.__init__') # self.__class__.__name__ DetectorImpl.__init__(self, *args, **kwargs) # caching self._geo_ = None self._pix_rc_ = None, None self._pix_xyz_ = None, None, None self._interpol_pars_ = None self._pedestals_ = None self._gain_ = None self._rms_ = None self._status_ = None self._common_mode_ = None self._mask_calib_ = None self._mask_ = None #logger.info('XXX dir(self):\n' + str(dir(self))) #logger.info('XXX self._segments:\n' + str(self._segments)) def raw(self,evt) -> Array3d: """ Returns dense 3-d numpy array of segment data from dict self._segments(evt) Parameters ---------- evt: event psana event object, ex. run.events().next(). Returns ------- raw data: np.array, ndim=3, shape: as data """ segs = self._segments(evt) if segs is None: logger.debug('self._segments(evt) is None') return None return arr3d_from_dict({k:v.raw for k,v in segs.items()}) def _segment_numbers(self,evt): """ Returns dense 1-d numpy array of segment indexes. from dict self._segments(evt) """ segs = self._segments(evt) if segs is None: logger.debug('self._segments(evt) is None') return None return np.array(sorted(segs.keys()), dtype=np.uint16) def _det_calibconst(self): logger.debug('AreaDetector._det_calibconst') cc = self._calibconst if cc is None: logger.debug('self._calibconst is None') return None return cc def _calibcons_and_meta_for_ctype(self, ctype='pedestals'): logger.debug('AreaDetector._calibcons_and_meta_for_ctype(ctype="%s")'%ctype) cc = self._det_calibconst() if cc is None: return None cons_and_meta = cc.get(ctype, None) if cons_and_meta is None: logger.debug('calibconst["%s"] is None'%ctype) return None, None return cons_and_meta def _cached_array(self, p, ctype='pedestals'): """cached array """ if p is None: p = self._calibcons_and_meta_for_ctype(ctype)[0] # 0-data/1-metadata return p def _pedestals(self): return self._cached_array(self._pedestals_, 'pedestals') def _gain(self): return self._cached_array(self._gain_, 'pixel_gain') def _rms(self): return self._cached_array(self._rms_, 'pixel_rms') def _status(self): return self._cached_array(self._status_, 'pixel_status') def _mask_calib(self):return self._cached_array(self._mask_calib_, 'pixel_mask') def _common_mode(self):return self._cached_array(self._common_mode_, 'common_mode') def _det_geotxt_and_meta(self): logger.debug('AreaDetector._det_geotxt_and_meta') cc = self._det_calibconst() if cc is None: return None geotxt_and_meta = cc.get('geometry', None) if geotxt_and_meta is None: logger.debug('calibconst[geometry] is None') return None, None return geotxt_and_meta def _det_geo(self): """ """ if self._geo_ is None: geotxt, meta = self._det_geotxt_and_meta() if geotxt is None: logger.debug('_det_geo geotxt is None') return None self._geo_ = GeometryAccess() self._geo_.load_pars_from_str(geotxt) return self._geo_ def _pixel_coord_indexes(self, **kwa): """ """ logger.debug('AreaDetector._pixel_coord_indexes') geo = self._det_geo() if geo is None: logger.debug('geo is None') return None return geo.get_pixel_coord_indexes(\ pix_scale_size_um = kwa.get('pix_scale_size_um',None),\ xy0_off_pix = kwa.get('xy0_off_pix',None),\ do_tilt = kwa.get('do_tilt',True),\ cframe = kwa.get('cframe',0)) def _pixel_coords(self, **kwa): """ """ logger.debug('AreaDetector._pixel_coords') geo = self._det_geo() if geo is None: logger.debug('geo is None') return None #return geo.get_pixel_xy_at_z(self, zplane=None, oname=None, oindex=0, do_tilt=True, cframe=0) return geo.get_pixel_coords(\ do_tilt = kwa.get('do_tilt',True),\ cframe = kwa.get('cframe',0)) def _cached_pixel_coord_indexes(self, evt, **kwa): """ """ logger.debug('AreaDetector._cached_pixel_coord_indexes') resp = self._pixel_coord_indexes(**kwa) if resp is None: return None # PRESERVE PIXEL INDEXES FOR USED SEGMENTS ONLY segs = self._segment_numbers(evt) if segs is None: return None logger.debug(info_ndarr(segs, 'preserve pixel indices for segments ')) rows, cols = self._pix_rc_ = [reshape_to_3d(a)[segs,:,:] for a in resp] #self._pix_rc_ = [dict_from_arr3d(reshape_to_3d(v)) for v in resp] s = 'evaluate_pixel_coord_indexes:' for i,a in enumerate(self._pix_rc_): s += info_ndarr(a, '\n %s '%('rows','cols')[i], last=3) logger.info(s) mapmode = kwa.get('mapmode',2) if mapmode <4: self.img_entries, self.dmulti_pix_to_img_idx, self.dmulti_imgidx_numentries=\ statistics_of_pixel_arrays(rows, cols) if mapmode==4: rsp = self._pixel_coords(**kwa) if rsp is None: return None x,y,z = self._pix_xyz_ = [reshape_to_3d(a)[segs,:,:] for a in rsp] self._interpol_pars_ = init_interpolation_parameters(rows, cols, x, y) if mapmode <4 and kwa.get('fillholes',True): self.img_pix_ascend_ind, self.img_holes, self.hole_rows, self.hole_cols, self.hole_inds1d =\ statistics_of_holes(rows, cols, **kwa) # TBD parameters for image interpolation if False: t0_sec = time() self.imgind_to_seg_row_col = image_of_pixel_seg_row_col(img_pix_ascend_ind, arr_shape) logger.debug('statistics_of_holes.imgind_to_seg_row_col time (sec) = %.6f' % (time()-t0_sec)) # 47ms logger.debug(info_ndarr(self.imgind_to_seg_row_col, ' imgind_to_seg_row_col ')) if False: s = ' imgind_to_seg_row_col ' # (n,352,384) first = (352+5)*384 + 380 for i in range(first,first+10): s += '\n s:%02d r:%03d c:%03d' % tuple(imgind_to_seg_row_col[i]) logger.debug(s) def calib(self, evt, **kwa) -> Array3d: """ """ logger.debug('%s.calib(evt) is implemented for generic case of area detector as raw - pedestals' % self.__class__.__name__\ +'\n If needed more, it needs to be re-implemented for this detector type.') raw = self.raw(evt) if raw is None: logger.debug('det.raw.raw(evt) is None') return None peds = self._pedestals() if peds is None: logger.debug('det.raw._pedestals() is None - return det.raw.raw(evt)') return raw return raw - peds def image(self, evt, nda=None, **kwa) -> Array2d: """ Create 2-d image. Parameters ---------- evt: event psana event object, ex. run.events().next(). mapmode: int, optional, default: 2 control on overlapping pixels on image map. 0/1/2/3/4: statistics of entries / last / max / mean pixel intensity / interpolated (TBD) - ascending data index. fillholes: bool, optional, default: True control on map bins inside the panel with 0 entries from data. True/False: fill empty bin with minimal intensity of four neares neighbors/ do not fill. vbase: float, optional, default: 0 value substituted for all image map bins without entry from data. Returns ------- image: np.array, ndim=2 """ logger.debug('in AreaDretector.image') if any(v is None for v in self._pix_rc_): self._cached_pixel_coord_indexes(evt, **kwa) if any(v is None for v in self._pix_rc_): return None vbase = kwa.get('vbase',0) mapmode = kwa.get('mapmode',2) fillholes = kwa.get('fillholes',True) if mapmode==0: return self.img_entries data = self.calib(evt) if nda is None else nda if data is None: logger.debug('AreaDetector.image calib returns None') return None #logger.debug(info_ndarr(data, 'data ', last=3)) rows, cols = self._pix_rc_ img = img_from_pixel_arrays(rows, cols, weight=data, vbase=vbase) # mapmode==1 if mapmode==2: img_multipixel_max(img, data, self.dmulti_pix_to_img_idx) elif mapmode==3: img_multipixel_mean(img, data, self.dmulti_pix_to_img_idx, self.dmulti_imgidx_numentries) if mapmode<4 and fillholes: fill_holes(img, self.hole_rows, self.hole_cols) return img if mapmode<4 else\ img_interpolated(data, self._interpol_pars_) if mapmode==4 else\ self.img_entries def _shape_as_daq(self): peds = self._pedestals() if peds is None: logger.debug('In _shape_as_daq pedestals are None, can not define daq data shape') return None return peds.shape if peds.ndim<4 else peds.shape[-3:] def _number_of_segments_total(self): shape = self._shape_as_daq() return None if shape is None else shape[-3] # (7,n,352,384) - define through calibration constants def _mask_default(self, dtype=DTYPE_MASK): shape = self._shape_as_daq() return None if shape is None else np.ones(shape, dtype=dtype) def _mask_calib_or_default(self, dtype=DTYPE_MASK): mask = self._mask_calib() return self._mask_default(dtype) if mask is None else mask.astype(dtype) def _mask_from_status(self, **kwa): """ For simple detectors w/o multi-gain ranges Parameters **kwa ---------------- ##mode - int 0/1/2 masks zero/four/eight neighbors around each bad pixel Returns ------- mask made of status: np.array, ndim=3, shape: as full detector data """ status = self._status() if status is None: logger.debug('pixel_status is None') return None return np.asarray(np.select((status>0,), (0,), default=1), dtype=DTYPE_MASK) def _mask_edges(self, **kwa): # -> Array3d: mask = self._mask_default(self, dtype=DTYPE_MASK) return None if mask is None else\ mask_edges(mask,\ edge_rows=kwa.get('edge_rows', 1),\ edge_cols=kwa.get('edge_cols', 1),\ dtype=DTYPE_MASK) # kwa.get('dtype', DTYPE_MASK)): # def _mask_neighbors(self, **kwa) -> Array3d: #mode = kwargs.get('mode', 0) #if mode: smask = gu.mask_neighbors(smask, allnbrs=(True if mode>=2 else False)) #segs = self._segments(evt) #if segs is None: # logger.debug('self._segments(evt) is None') # return None #return arr3d_from_dict({k:v.raw for k,v in segs.items()}) def _mask(self, calib=False, status=False, edges=False, neighbors=False, **kwa): """Returns per-pixel array with mask values (per-pixel product of all requested masks). Parameters - calib : bool - True/False = on/off mask from calib directory. - status : bool - True/False = on/off mask generated from calib pixel_status. - edges : bool - True/False = on/off mask of edges. - neighbors: bool - True/False = on/off mask of neighbors. - kwa : dict - additional parameters passed to low level methods (width,...) for edges: edge_rows=1, edge_cols=1, center_rows=0, center_cols=0, dtype=DTYPE_MASK for status of epix10ka: grinds=(0,1,2,3,4) Returns - np.array - per-pixel mask values 1/0 for good/bad pixels. """ dtype = kwa.get('dtype', DTYPE_MASK) mask = self._mask_calib_or_default(dtype) if calib else self._mask_default(dtype) if status: mask = merge_masks(mask, self._mask_from_status(**kwa)) if edges: mask = merge_masks(mask, self._mask_edges(**kwa)) #if neighbors: mask = merge_masks(mask, self._mask_neighbors(self, **kwa)) return mask def _mask_comb(self, **kwa): mbits=kwa.get('mbits', 1) return self._mask(\ calib = mbits & 1,\ status = mbits & 2,\ edges = mbits & 4,\ neighbors = mbits & 8,\ **kwa)
class AreaDetector(DetectorImpl): def __init__(self, *args, **kwargs): logger.debug('AreaDetector.__init__') # self.__class__.__name__ DetectorImpl.__init__(self, *args, **kwargs) # caching self.geo = None self.pix_rc = None, None self.pix_xyz = None, None, None self.interpol_pars = None #logger.info('XXX dir(self):\n' + str(dir(self))) #logger.info('XXX self._segments:\n' + str(self._segments)) # example of some possible common behavior def _common_mode(self, *args, **kwargs): """ """ logger.debug('in %s._common_mode' % self.__class__.__name__) pass def raw(self, evt): """ Returns dense 3-d numpy array of segment data from dict self._segments(evt) """ segs = self._segments(evt) if segs is None: logger.warning('self._segments(evt) is None') return None return arr3d_from_dict({k: v.raw for k, v in segs.items()}) def segments(self, evt): """ Returns dense 1-d numpy array of segment indexes. from dict self._segments(evt) """ segs = self._segments(evt) if segs is None: logger.warning('self._segments(evt) is None') return None return np.array(sorted(segs.keys()), dtype=np.uint16) def det_calibconst(self): logger.debug('AreaDetector.det_calibconst') cc = self._calibconst if cc is None: logger.warning('self._calibconst is None') return None return cc def det_geotxt_and_meta(self): logger.debug('AreaDetector.det_geometry_txt') cc = self.det_calibconst() if cc is None: return None geotxt_and_meta = cc.get('geometry', None) if geotxt_and_meta is None: logger.warning('calibconst[geometry] is None') return None, None return geotxt_and_meta def det_geo(self): """ """ if self.geo is None: geotxt, meta = self.det_geotxt_and_meta() if geotxt is None: logger.warning('det_geo geotxt is None') return None self.geo = GeometryAccess() self.geo.load_pars_from_str(geotxt) return self.geo def pixel_coord_indexes(self, **kwa): """ """ logger.debug('AreaDetector.pixel_coord_indexes') geo = self.det_geo() if geo is None: logger.warning('geo is None') return None return geo.get_pixel_coord_indexes(\ pix_scale_size_um = kwa.get('pix_scale_size_um',None),\ xy0_off_pix = kwa.get('xy0_off_pix',None),\ do_tilt = kwa.get('do_tilt',True),\ cframe = kwa.get('cframe',0)) def pixel_coords(self, **kwa): """ """ logger.debug('AreaDetector.pixel_coords') geo = self.det_geo() if geo is None: logger.warning('geo is None') return None #return geo.get_pixel_xy_at_z(self, zplane=None, oname=None, oindex=0, do_tilt=True, cframe=0) return geo.get_pixel_coords(\ do_tilt = kwa.get('do_tilt',True),\ cframe = kwa.get('cframe',0)) def cached_pixel_coord_indexes(self, evt, **kwa): """ """ logger.debug('AreaDetector.cached_pixel_coord_indexes') resp = self.pixel_coord_indexes(**kwa) if resp is None: return None # PRESERVE PIXEL INDEXES FOR USED SEGMENTS ONLY segs = self.segments(evt) if segs is None: return None logger.debug(info_ndarr(segs, 'preserve pixel indices for segments ')) rows, cols = self.pix_rc = [reshape_to_3d(a)[segs, :, :] for a in resp] #self.pix_rc = [dict_from_arr3d(reshape_to_3d(v)) for v in resp] s = 'evaluate_pixel_coord_indexes:' for i, a in enumerate(self.pix_rc): s += info_ndarr(a, '\n %s ' % ('rows', 'cols')[i], last=3) logger.info(s) mapmode = kwa.get('mapmode', 2) if mapmode < 4: self.img_entries, self.dmulti_pix_to_img_idx, self.dmulti_imgidx_numentries=\ statistics_of_pixel_arrays(rows, cols) if mapmode == 4: rsp = self.pixel_coords(**kwa) if rsp is None: return None x, y, z = self.pix_xyz = [ reshape_to_3d(a)[segs, :, :] for a in rsp ] self.interpol_pars = init_interpolation_parameters( rows, cols, x, y) if mapmode < 4 and kwa.get('fillholes', True): self.img_pix_ascend_ind, self.img_holes, self.hole_rows, self.hole_cols, self.hole_inds1d =\ statistics_of_holes(rows, cols, **kwa) # TBD parameters for image interpolation if False: t0_sec = time() self.imgind_to_seg_row_col = image_of_pixel_seg_row_col( img_pix_ascend_ind, arr_shape) logger.debug( 'statistics_of_holes.imgind_to_seg_row_col time (sec) = %.6f' % (time() - t0_sec)) # 47ms logger.debug( info_ndarr(self.imgind_to_seg_row_col, ' imgind_to_seg_row_col ')) if False: s = ' imgind_to_seg_row_col ' # (n,352,384) first = (352 + 5) * 384 + 380 for i in range(first, first + 10): s += '\n s:%02d r:%03d c:%03d' % tuple( imgind_to_seg_row_col[i]) logger.debug(s) def calib(self, evt): """ """ logger.warning('AreaDetector.calib TBD currently returns raw') #print('XXX dir(self):', dir(self)) #cc = self.det_calibconst() #if cc is None: return None #peds, peds_meta = cc.get('pedestals', None) #... return self.raw(evt) def image(self, evt, nda=None, **kwa): logger.debug('in AreaDretector.image') if any(v is None for v in self.pix_rc): self.cached_pixel_coord_indexes(evt, **kwa) if any(v is None for v in self.pix_rc): return None vbase = kwa.get('vbase', 0) mapmode = kwa.get('mapmode', 2) fillholes = kwa.get('fillholes', True) if mapmode == 0: return self.img_entries data = self.calib(evt) if nda is None else nda if data is None: logger.warning('AreaDetector.image calib returns None') return None #logger.debug(info_ndarr(data, 'data ', last=3)) rows, cols = self.pix_rc img = img_from_pixel_arrays(rows, cols, weight=data, vbase=vbase) # mapmode==1 if mapmode == 2: img_multipixel_max(img, data, self.dmulti_pix_to_img_idx) elif mapmode == 3: img_multipixel_mean(img, data, self.dmulti_pix_to_img_idx, self.dmulti_imgidx_numentries) if mapmode < 4 and fillholes: fill_holes(img, self.hole_rows, self.hole_cols) return img if mapmode<4 else\ img_interpolated(data, self.interpol_pars) if mapmode==4 else\ self.img_entries
class CalibConstants: def __init__(self, calibconst, **kwa): """ Parameters ---------- calibconst: dict - retrieved from DB by method calib_constants_all_types from psana.pscalib.calib.MDBWebUtils. **kwa: not used """ logger.debug('__init__') # self.__class__.__name__ assert isinstance(calibconst, dict), 'Input parameter should be dict: %s' % str(calibconst) self._calibconst = calibconst self._geo = None self._pedestals = None self._gain = None # ADU/eV self._gain_factor = None # keV/ADU self._rms = None self._status = None self._common_mode = None self._mask_calib = None self._pix_rc = None, None self._pix_xyz = None, None, None self._interpol_pars = None def calibconst(self): logger.debug('calibconst') cc = self._calibconst if ut.is_none(cc, 'self._calibconst is None'): return None return cc def cons_and_meta_for_ctype(self, ctype='pedestals'): logger.debug('cons_and_meta_for_ctype(ctype="%s")'%ctype) cc = self.calibconst() if cc is None: return None cons_and_meta = cc.get(ctype, None) if ut.is_none(cons_and_meta, 'calibconst["%s"] is None'%ctype): return None, None return cons_and_meta def cached_array(self, p, ctype='pedestals'): """returns cached array of constants for ctype """ if p is None: p = self.cons_and_meta_for_ctype(ctype)[0] # 0-data/1-metadata return p def pedestals(self): return self.cached_array(self._pedestals, 'pedestals') def rms(self): return self.cached_array(self._rms, 'pixel_rms') def common_mode(self): return self.cached_array(self._common_mode, 'common_mode') def gain(self): return self.cached_array(self._gain, 'pixel_gain') def mask_calib(self): a = self.cached_array(self._mask_calib, 'pixel_mask') return a if a is None else a.astype(DTYPE_MASK) def status(self): a = self.cached_array(self._status, 'pixel_status') return a if a is None else a.astype(DTYPE_STATUS) def gain_factor(self): """Evaluate and return gain factor as 1/gain if gain is available in calib constants else 1.""" if self._gain_factor is None: g = self.gain() self._gain_factor = divide_protected(np.ones_like(g), g) if isinstance(g, np.ndarray) else 1 return self._gain_factor def shape_as_daq(self): peds = self.pedestals() if ut.is_none(peds, 'shape_as_daq - pedestals is None, can not define daq data shape - returns None'): return None return peds.shape if peds.ndim<4 else peds.shape[-3:] def number_of_segments_total(self): shape = self.shape_as_daq() return None if shape is None else shape[-3] # (7,n,352,384) - define through calibration constants def segment_numbers_total(self): """returns total list list of segment numbers.""" nsegs = self.number_of_segments_total() segnums = None if ut.is_none(nsegs, 'number_of_segments_total is None') else\ list(range(nsegs)) #tuple(np.arange(nsegs, dtype=np.uint16)) logger.debug('segnums: %s' % str(segnums)) return segnums def geotxt_and_meta(self): logger.debug('geotxt_and_meta') cc = self.calibconst() if cc is None: return None geotxt_and_meta = cc.get('geometry', None) if ut.is_none(geotxt_and_meta, 'calibconst[geometry] is None'): return None, None return geotxt_and_meta def geotxt_default(self): logger.debug('geotxt_default should be re-implemented in specific detector subclass, othervise returns None') return None def geo(self): """ return GeometryAccess() object """ if self._geo is None: geotxt, meta = self.geotxt_and_meta() if geotxt is None: geotxt = self.geotxt_default() if ut.is_none(geotxt, 'geo geotxt is None'): return None self._geo = GeometryAccess() self._geo.load_pars_from_str(geotxt) return self._geo def seg_geo(self): logger.debug('pixel_coords') geo = self.geo() if ut.is_none(geo, 'geo is None'): return None return None if ut.is_none(geo, 'geo is None') else\ geo.get_seg_geo().algo def pixel_coords(self, **kwa): """ returns x, y, z - three np.ndarray """ logger.debug('pixel_coords') geo = self.geo() if ut.is_none(geo, 'geo is None'): return None #return geo.get_pixel_xy_at_z(self, zplane=None, oname=None, oindex=0, do_tilt=True, cframe=0) return geo.get_pixel_coords(\ do_tilt = kwa.get('do_tilt',True),\ cframe = kwa.get('cframe',0)) def pixel_coord_indexes(self, **kwa): """ returns ix, iy - two np.ndarray """ logger.debug('pixel_coord_indexes') geo = self.geo() if ut.is_none(geo, 'geo is None'): return None return geo.get_pixel_coord_indexes(\ pix_scale_size_um = kwa.get('pix_scale_size_um',None),\ xy0_off_pix = kwa.get('xy0_off_pix',None),\ do_tilt = kwa.get('do_tilt',True),\ cframe = kwa.get('cframe',0)) def cached_pixel_coord_indexes(self, segnums=None, **kwa): """ """ logger.debug('CalibConstants.cached_pixel_coord_indexes') resp = self.pixel_coord_indexes(**kwa) if resp is None: return None # PRESERVE PIXEL INDEXES FOR USED SEGMENTS ONLY if segnums is None: segnums = self.segment_numbers_total() logger.info(info_ndarr(segnums, 'preserve pixel indices for segments ')) rows, cols = self._pix_rc = [reshape_to_3d(a)[segnums,:,:] for a in resp] #self._pix_rc = [dict_from_arr3d(reshape_to_3d(v)) for v in resp] s = 'evaluate_pixel_coord_indexes:' for i,a in enumerate(self._pix_rc): s += info_ndarr(a, '\n %s '%('rows','cols')[i], last=3) logger.info(s) mapmode = kwa.get('mapmode',2) fillholes = kwa.get('fillholes',True) if mapmode <4: self.img_entries, self.dmulti_pix_to_img_idx, self.dmulti_imgidx_numentries=\ statistics_of_pixel_arrays(rows, cols) if mapmode==4: rsp = self._pixel_coords(**kwa) if rsp is None: return None x,y,z = self._pix_xyz = [reshape_to_3d(a)[segnums,:,:] for a in rsp] self._interpol_pars = init_interpolation_parameters(rows, cols, x, y) if mapmode <4 and fillholes: self.img_pix_ascend_ind, self.img_holes, self.hole_rows, self.hole_cols, self.hole_inds1d =\ statistics_of_holes(rows, cols, **kwa) # TBD parameters for image interpolation if False: t0_sec = time() self.imgind_to_seg_row_col = image_of_pixel_seg_row_col(img_pix_ascend_ind, arr_shape) logger.debug('statistics_of_holes.imgind_to_seg_row_col time (sec) = %.6f' % (time()-t0_sec)) # 47ms logger.debug(info_ndarr(self.imgind_to_seg_row_col, ' imgind_to_seg_row_col ')) if False: s = ' imgind_to_seg_row_col ' # (n,352,384) first = (352+5)*384 + 380 for i in range(first,first+10): s += '\n s:%02d r:%03d c:%03d' % tuple(imgind_to_seg_row_col[i]) logger.debug(s) def image(self, nda, segnums=None, **kwa): """ Create 2-d image. Parameters ---------- nda: np.array, ndim=3 array shaped as daq raw data. segnums: list/tuple of segment (uint) indexes mapmode: int, optional, default: 2 control on overlapping pixels on image map. 0/1/2/3/4: statistics of entries / last / max / mean pixel intensity / interpolated (TBD) - ascending data index. fillholes: bool, optional, default: True control on map bins inside the panel with 0 entries from data. True/False: fill empty bin with minimal intensity of four neares neighbors/ do not fill. vbase: float, optional, default: 0 value substituted for all image map bins without entry from data. Returns ------- image: np.array, ndim=2 """ logger.debug('in CalibConstants.image') if any(v is None for v in self._pix_rc): self.cached_pixel_coord_indexes(segnums, **kwa) if any(v is None for v in self._pix_rc): return None vbase = kwa.get('vbase',0) mapmode = kwa.get('mapmode',2) fillholes = kwa.get('fillholes',True) if mapmode==0: return self.img_entries if ut.is_none(nda, 'CalibConstants.image calib returns None'): return None logger.debug(info_ndarr(nda, 'nda ', last=3)) rows, cols = self._pix_rc logger.debug(info_ndarr(rows, 'rows ', last=3)) logger.debug(info_ndarr(cols, 'cols ', last=3)) img = img_from_pixel_arrays(rows, cols, weight=nda, vbase=vbase) # mapmode==1 if mapmode==2: img_multipixel_max(img, nda, self.dmulti_pix_to_img_idx) elif mapmode==3: img_multipixel_mean(img, nda, self.dmulti_pix_to_img_idx, self.dmulti_imgidx_numentries) if mapmode<4 and fillholes: fill_holes(img, self.hole_rows, self.hole_cols) return img if mapmode<4 else\ img_interpolated(nda, self._cached_interpol_pars()) if mapmode==4 else\ self.img_entries def pix_rc(self): return self._pix_rc def pix_xyz(self): return self._pix_xyz def interpol_pars(self): return self._interpol_pars
class AreaDetector(DetectorImpl): def __init__(self, *args, **kwargs): logger.debug('AreaDetector.__init__') # self.__class__.__name__ DetectorImpl.__init__(self, *args, **kwargs) # caching self._geo_ = None self._pix_rc_ = None, None self._pix_xyz_ = None, None, None self._interpol_pars_ = None self._pedestals_ = None self._gain_ = None # ADU/eV self._gain_factor_ = None # keV/ADU self._rms_ = None self._status_ = None self._common_mode_ = None self._mask_calib_ = None self._mask_ = None #logger.info('XXX dir(self):\n' + str(dir(self))) #logger.info('XXX self._segments:\n' + str(self._segments)) def raw(self, evt) -> Array3d: """ Returns dense 3-d numpy array of segment data from dict self._segments(evt) Parameters ---------- evt: event psana event object, ex. run.events().next(). Returns ------- raw data: np.array, ndim=3, shape: as data """ if evt is None: return None segs = self._segments(evt) if is_none(segs, 'self._segments(evt) is None'): return None return arr3d_from_dict({k: v.raw for k, v in segs.items()}) def _segment_numbers(self, evt): """ Returns dense 1-d numpy array of segment indexes. from dict self._segments(evt) """ segs = self._segments(evt) if is_none(segs, 'self._segments(evt) is None'): return None return np.array(sorted(segs.keys()), dtype=np.uint16) def _det_calibconst(self): logger.debug('AreaDetector._det_calibconst') cc = self._calibconst if is_none(cc, 'self._calibconst is None'): return None return cc def _calibcons_and_meta_for_ctype(self, ctype='pedestals'): logger.debug('AreaDetector._calibcons_and_meta_for_ctype(ctype="%s")' % ctype) cc = self._det_calibconst() if cc is None: return None cons_and_meta = cc.get(ctype, None) if is_none(cons_and_meta, 'calibconst["%s"] is None' % ctype): return None, None return cons_and_meta def _cached_array(self, p, ctype='pedestals'): """cached array """ if p is None: p = self._calibcons_and_meta_for_ctype(ctype)[ 0] # 0-data/1-metadata return p def _pedestals(self): return self._cached_array(self._pedestals_, 'pedestals') def _rms(self): return self._cached_array(self._rms_, 'pixel_rms') def _status(self): return self._cached_array(self._status_, 'pixel_status') def _mask_calib(self): return self._cached_array(self._mask_calib_, 'pixel_mask') def _common_mode(self): return self._cached_array(self._common_mode_, 'common_mode') def _gain(self): return self._cached_array(self._gain_, 'pixel_gain') def _gain_factor(self): """Evaluate and return gain factor as 1/gain if gain is available in calib constants else 1.""" if self._gain_factor_ is None: g = self._gain() self._gain_factor_ = divide_protected( np.ones_like(g), g) if isinstance(g, np.ndarray) else 1 return self._gain_factor_ def _det_geotxt_and_meta(self): logger.debug('AreaDetector._det_geotxt_and_meta') cc = self._det_calibconst() if cc is None: return None geotxt_and_meta = cc.get('geometry', None) if is_none(geotxt_and_meta, 'calibconst[geometry] is None'): return None, None return geotxt_and_meta def _det_geotxt_default(self): logger.debug( '_det_geotxt_default should be re-implemented in specific detector subclass, othervise returns None' ) return None def _det_geo(self): """ """ if self._geo_ is None: geotxt, meta = self._det_geotxt_and_meta() if geotxt is None: geotxt = self._det_geotxt_default() if is_none(geotxt, '_det_geo geotxt is None'): return None self._geo_ = GeometryAccess() self._geo_.load_pars_from_str(geotxt) return self._geo_ def _pixel_coord_indexes(self, **kwa): """ """ logger.debug('AreaDetector._pixel_coord_indexes') geo = self._det_geo() if is_none(geo, 'geo is None'): return None return geo.get_pixel_coord_indexes(\ pix_scale_size_um = kwa.get('pix_scale_size_um',None),\ xy0_off_pix = kwa.get('xy0_off_pix',None),\ do_tilt = kwa.get('do_tilt',True),\ cframe = kwa.get('cframe',0)) def _pixel_coords(self, **kwa): """ """ logger.debug('AreaDetector._pixel_coords') geo = self._det_geo() if is_none(geo, 'geo is None'): return None #return geo.get_pixel_xy_at_z(self, zplane=None, oname=None, oindex=0, do_tilt=True, cframe=0) return geo.get_pixel_coords(\ do_tilt = kwa.get('do_tilt',True),\ cframe = kwa.get('cframe',0)) def _cached_pixel_coord_indexes(self, evt, **kwa): """ """ logger.debug('AreaDetector._cached_pixel_coord_indexes') resp = self._pixel_coord_indexes(**kwa) if resp is None: return None # PRESERVE PIXEL INDEXES FOR USED SEGMENTS ONLY segs = self._segment_numbers(evt) if segs is None: return None logger.debug(info_ndarr(segs, 'preserve pixel indices for segments ')) rows, cols = self._pix_rc_ = [ reshape_to_3d(a)[segs, :, :] for a in resp ] #self._pix_rc_ = [dict_from_arr3d(reshape_to_3d(v)) for v in resp] s = 'evaluate_pixel_coord_indexes:' for i, a in enumerate(self._pix_rc_): s += info_ndarr(a, '\n %s ' % ('rows', 'cols')[i], last=3) logger.info(s) mapmode = kwa.get('mapmode', 2) if mapmode < 4: self.img_entries, self.dmulti_pix_to_img_idx, self.dmulti_imgidx_numentries=\ statistics_of_pixel_arrays(rows, cols) if mapmode == 4: rsp = self._pixel_coords(**kwa) if rsp is None: return None x, y, z = self._pix_xyz_ = [ reshape_to_3d(a)[segs, :, :] for a in rsp ] self._interpol_pars_ = init_interpolation_parameters( rows, cols, x, y) if mapmode < 4 and kwa.get('fillholes', True): self.img_pix_ascend_ind, self.img_holes, self.hole_rows, self.hole_cols, self.hole_inds1d =\ statistics_of_holes(rows, cols, **kwa) # TBD parameters for image interpolation if False: t0_sec = time() self.imgind_to_seg_row_col = image_of_pixel_seg_row_col( img_pix_ascend_ind, arr_shape) logger.debug( 'statistics_of_holes.imgind_to_seg_row_col time (sec) = %.6f' % (time() - t0_sec)) # 47ms logger.debug( info_ndarr(self.imgind_to_seg_row_col, ' imgind_to_seg_row_col ')) if False: s = ' imgind_to_seg_row_col ' # (n,352,384) first = (352 + 5) * 384 + 380 for i in range(first, first + 10): s += '\n s:%02d r:%03d c:%03d' % tuple( imgind_to_seg_row_col[i]) logger.debug(s) def calib(self, evt, **kwa) -> Array3d: """ """ logger.debug('%s.calib(evt) is implemented for generic case of area detector as raw - pedestals' % self.__class__.__name__\ +'\n If needed more, it needs to be re-implemented for this detector type.') raw = self.raw(evt) if is_none(raw, 'det.raw.raw(evt) is None'): return None peds = self._pedestals() if is_none(peds, 'det.raw._pedestals() is None - return det.raw.raw(evt)'): return raw arr = raw - peds gfac = self._gain_factor() return arr * gfac if gfac != 1 else arr def image(self, evt, nda=None, **kwa) -> Array2d: """ Create 2-d image. Parameters ---------- evt: event psana event object, ex. run.events().next(). mapmode: int, optional, default: 2 control on overlapping pixels on image map. 0/1/2/3/4: statistics of entries / last / max / mean pixel intensity / interpolated (TBD) - ascending data index. fillholes: bool, optional, default: True control on map bins inside the panel with 0 entries from data. True/False: fill empty bin with minimal intensity of four neares neighbors/ do not fill. vbase: float, optional, default: 0 value substituted for all image map bins without entry from data. Returns ------- image: np.array, ndim=2 """ logger.debug('in AreaDretector.image') if any(v is None for v in self._pix_rc_): self._cached_pixel_coord_indexes(evt, **kwa) if any(v is None for v in self._pix_rc_): return None vbase = kwa.get('vbase', 0) mapmode = kwa.get('mapmode', 2) fillholes = kwa.get('fillholes', True) if mapmode == 0: return self.img_entries data = self.calib(evt) if nda is None else nda if is_none(data, 'AreaDetector.image calib returns None'): return None #logger.debug(info_ndarr(data, 'data ', last=3)) rows, cols = self._pix_rc_ img = img_from_pixel_arrays(rows, cols, weight=data, vbase=vbase) # mapmode==1 if mapmode == 2: img_multipixel_max(img, data, self.dmulti_pix_to_img_idx) elif mapmode == 3: img_multipixel_mean(img, data, self.dmulti_pix_to_img_idx, self.dmulti_imgidx_numentries) if mapmode < 4 and fillholes: fill_holes(img, self.hole_rows, self.hole_cols) return img if mapmode<4 else\ img_interpolated(data, self._interpol_pars_) if mapmode==4 else\ self.img_entries def _shape_as_daq(self): peds = self._pedestals() if is_none( peds, 'In _shape_as_daq pedestals is None, can not define daq data shape - returns None' ): return None return peds.shape if peds.ndim < 4 else peds.shape[-3:] def _number_of_segments_total(self): shape = self._shape_as_daq() return None if shape is None else shape[ -3] # (7,n,352,384) - define through calibration constants def _mask_default(self, dtype=DTYPE_MASK): shape = self._shape_as_daq() return None if shape is None else np.ones(shape, dtype=dtype) def _mask_calib_or_default(self, dtype=DTYPE_MASK): mask = self._mask_calib() return self._mask_default(dtype) if mask is None else mask.astype( dtype) def _mask_from_status(self, status_bits=0xffff, dtype=DTYPE_MASK, **kwa): """ Mask made of pixel_status calibration constants. Parameters **kwa ---------------- - status_bits : uint - bitword for mask status codes. - dtype : numpy.dtype - mask np.array dtype Returns ------- np.array - mask made of pixel_status calibration constants, shapeed as full detector data """ status = self._status() if is_none(status, 'pixel_status is None'): return None return um.status_as_mask(status, status_bits=status_bits, dtype=DTYPE_MASK, **kwa) def _mask_neighbors(self, mask, rad=9, ptrn='r'): """Returns 2-d or n-d mask array with increased by radial paramerer rad region around all 0-pixels in the input mask. Parameter - mask : np.array - input mask of good/bad (1/0) pixels - rad : int - radisl parameter for masking region aroung each 0-pixel in mask - ptrn : char - pattern of the masked region, ptrn='r'-rhombus, 'c'-circle, othervise square [-rad,+rad] in rows and columns. Returns - np.array - mask with masked neighbors, shape = mask.shape """ return um.mask_neighbors(mask, rad, ptrn) def _mask_edges(self, width=0, edge_rows=1, edge_cols=1, dtype=DTYPE_MASK, **kwa): return um.mask_edges(self._mask_default(),\ width=width, edge_rows=edge_rows, edge_cols=edge_cols, dtype=dtype, **kwa) def _mask_center(self, wcenter=0, center_rows=1, center_cols=1, dtype=DTYPE_MASK, **kwa): """ Parameters ---------- center_rows: int number of edge rows to mask for all ASICs center_cols: int number of edge columns to mask for all ASICs dtype: numpy dtype of the output array **kwa: is not used Returns ------- mask: np.ndarray, ndim=3, shaped as full detector data, mask of the panel and asic edges """ logger.debug('epix_base._mask_edges') mask1 = self._seg_geo.pixel_mask_array(width=0, edge_rows=0, edge_cols=0,\ center_rows=center_rows, center_cols=center_cols, dtype=dtype, **kwa) nsegs = self._number_of_segments_total() if is_none(nsegs, '_number_of_segments_total is None'): return None return np.stack([mask1 for i in range(nsegs)]) def _mask_comb(self, status=True, neighbors=False, edges=False, center=False, calib=False, umask=None, dtype=DTYPE_MASK, **kwa): """Returns combined mask controlled by the keyword arguments. Parameters ---------- - status : bool : True - mask from pixel_status constants, kwa: status_bits=0xffff - status bits to use in mask. Status bits show why pixel is considered as bad. Content of the bitword depends on detector and code version. It is wise to exclude pixels with any bad status by setting status_bits=0xffff. kwa: gain_range_inds=(0,1,2,3,4) - list of gain range indexes to merge for epix10ka or jungfrau - neighbor : bool : False - mask of neighbors of all bad pixels, kwa: rad=5 - radial parameter of masked region kwa: ptrn='r'-rhombus, 'c'-circle, othervise square region around each bad pixel - edges : bool : False - mask edge rows and columns of each panel, kwa: width=0 or edge_rows=1, edge_cols=1 - number of masked edge rows, columns - center : bool : False - mask center rows and columns of each panel consisting of ASICS (cspad, epix, jungfrau), kwa: wcenter=0 or center_rows=1, center_cols=1 - number of masked center rows and columns in the segment, works for cspad2x1, epix100, epix10ka, jungfrau panels - calib : bool : False - apply user's defined mask from pixel_mask constants - umask : np.array: None - apply user's defined mask from input parameters (shaped as data) Returns ------- np.array: dtype=np.uint8, shape as det.raw - mask array of 1 or 0 or None if all switches are False. """ mask = None if status: status_bits = kwa.get('status_bits', 0xffff) gain_range_inds = kwa.get('gain_range_inds', (0, 1, 2, 3, 4)) # works for epix10ka mask = self._mask_from_status(status_bits=0xffff, gain_range_inds=gain_range_inds, dtype=dtype) # if unbond and (self.is_cspad2x2() or self.is_cspad()): # mask_unbond = self.mask_geo(par, width=0, wcenter=0, mbits=4) # mbits=4 - unbonded pixels for cspad2x1 segments # mask = mask_unbond if mask is None else um.merge_masks(mask, mask_unbond) if neighbors and mask is not None: rad = kwa.get('rad', 5) ptrn = kwa.get('ptrn', 'r') mask = um.mask_neighbors(mask, rad=rad, ptrn=ptrn) if edges: width = kwa.get('width', 0) erows = kwa.get('edge_rows', 1) ecols = kwa.get('edge_cols', 1) mask_edges = self._mask_edges( width=width, edge_rows=erows, edge_cols=ecols, dtype=dtype) # masks each segment edges only mask = mask_edges if mask is None else um.merge_masks( mask, mask_edges, dtype=dtype) if center: wcent = kwa.get('wcenter', 0) crows = kwa.get('center_rows', 1) ccols = kwa.get('center_cols', 1) mask_center = self._mask_center(wcenter=wcent, center_rows=crows, center_cols=ccols, dtype=dtype) mask = mask_center if mask is None else um.merge_masks( mask, mask_center, dtype=dtype) if calib: mask_calib = self._mask_calib_or_default(dtype=dtype) mask = mask_calib if mask is None else um.merge_masks( mask, mask_calib, dtype=dtype) if umask is not None: mask = umask if mask is None else um.merge_masks( mask, umask, dtype=dtype) return mask def _mask(self, status=True, neighbors=False, edges=False, center=False, calib=False, umask=None, force_update=False, dtype=DTYPE_MASK, **kwa): """returns cached mask. """ if self._mask_ is None or force_update: self._mask_ = self._mask_comb(status=status, neighbors=neighbors, edges=edges, center=center, calib=calib, umask=umask, dtype=dtype, **kwa) return self._mask_