Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:48:47

0001 #!/usr/bin/env python
0002 #
0003 # Copyright (c) 2019 Opticks Team. All Rights Reserved.
0004 #
0005 # This file is part of Opticks
0006 # (see https://bitbucket.org/simoncblyth/opticks).
0007 #
0008 # Licensed under the Apache License, Version 2.0 (the "License"); 
0009 # you may not use this file except in compliance with the License.  
0010 # You may obtain a copy of the License at
0011 #
0012 #   http://www.apache.org/licenses/LICENSE-2.0
0013 #
0014 # Unless required by applicable law or agreed to in writing, software 
0015 # distributed under the License is distributed on an "AS IS" BASIS, 
0016 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
0017 # See the License for the specific language governing permissions and 
0018 # limitations under the License.
0019 #
0020 
0021 """
0022 
0023 ::
0024 
0025    
0026     ipython --pdb -i evt.py -- --pfx tds3gun --src natural --tag 100
0027 
0028 
0029     #export OPTICKS_ANA_DEFAULTS="det=tboolean-box,src=torch,tag=1,pfx=tboolean-box"
0030     #export OPTICKS_EVENT_BASE=/tmp       # <-- set this to the test invokation directory 
0031 
0032     unset OPTICKS_ANA_DEFAULTS
0033     unset OPTICKS_EVENT_BASE
0034     LV=box evt.py 
0035 
0036 
0037 """
0038 import os, logging, stat, datetime
0039 import numpy as np
0040 
0041 m0_ = lambda p:np.sqrt(np.sum(p*p, axis=0))  # magnitude axis 0 
0042 m1_ = lambda p:np.sqrt(np.sum(p*p, axis=1))  # magnitude axis 1 
0043 m2_ = lambda p:np.sqrt(np.sum(p*p, axis=2))  # magnitude axis 2
0044 
0045 
0046 nb_ = lambda a,s:( a & ( 0xf << 4*s )) >> (4*s)   # nibble slot s in array a 
0047 
0048 
0049 #from opticks.ana.debug import MyPdb
0050 """
0051 # not working with py3
0052 try:
0053     from IPython.core.debugger import Pdb as MyPdb
0054 except ImportError:
0055     class MyPdb(object):
0056         def set_trace(self):
0057             log.error("IPython is required for ipdb.set_trace() " )
0058         pass  
0059     pass
0060 pass
0061 ipdb = MyPdb()
0062 """
0063 ipdb = None
0064 
0065 #  dont use matplotlib in base modules as it fails remotely 
0066 #     qt.qpa.screen: QXcbConnection: Could not connect to display Could not connect to any X display.
0067 #try:
0068 #    import matplotlib.pyplot as plt
0069 #except ImportError:
0070 #    plt = None
0071 #pass
0072 
0073 
0074 from collections import OrderedDict as odict
0075 
0076 from opticks.ana.base import stamp_
0077 from opticks.ana.num import _slice, Num
0078 from opticks.ana.ctx import Ctx
0079 from opticks.ana.nbase import count_unique, vnorm
0080 from opticks.ana.nload import A, I, II, tagdir_
0081 from opticks.ana.seq import SeqAna, seq2msk, SeqList
0082 from opticks.ana.histype import HisType    # for use with seqhis
0083 from opticks.ana.hismask import HisMask    # for use with pflags   
0084 from opticks.ana.mattype import MatType 
0085 from opticks.ana.metadata import Metadata
0086 from opticks.ana.nibble import msk_, nib_, make_msk, make_nib
0087 from opticks.ana.blib import BLib
0088 
0089 msk = make_msk(16)
0090 NIB = make_nib(16)
0091 
0092 
0093 def count_nonzero(a):
0094     return np.sum(a != 0)
0095 
0096 if not hasattr(np, 'count_nonzero'): np.count_nonzero = count_nonzero
0097 
0098 pdict_ = lambda d:" ".join(["%s:%s" % kv for kv in sorted(d.items(),key=lambda kv:kv[0]) ])
0099 costheta_ = lambda a,b:np.sum(a * b, axis = 1)/(vnorm(a)*vnorm(b)) 
0100 ntile_ = lambda vec,N:np.tile(vec, N).reshape(-1, len(vec))
0101 cross_ = lambda a,b:np.cross(a,b)/np.repeat(vnorm(a),3).reshape(-1,3)/np.repeat(vnorm(b),3).reshape(-1,3)
0102 norm_ = lambda a:a/np.repeat(vnorm(a), 3).reshape(-1,3)
0103 
0104 
0105 deg = np.pi/180.
0106 
0107 log = logging.getLogger(__name__)
0108 blib = BLib()
0109 
0110 
0111 
0112 X,Y,Z,W,T = 0,1,2,3,3
0113 
0114   
0115 
0116 class Evt(object):
0117     """
0118     Using interactive high level *sel* selection::
0119 
0120         In [9]: a.sel = "TO BT BT BT BT DR SA"
0121         [2016-11-11 13:23:06,182] p78027 {/Users/blyth/opticks/ana/evt.py:546} INFO - psel array([False, False, False, ..., False, False, False], dtype=bool) 
0122         [2016-11-11 13:23:06,184] p78027 {/Users/blyth/opticks/ana/evt.py:553} INFO - _init_selection nsel 7540 len(psel) 1000000  
0123 
0124         In [10]: a.his
0125         Out[10]: 
0126         .                                noname 
0127         .                                  7540         1.00 
0128            0              89ccccd        1.000           7540         [7 ] TO BT BT BT BT DR SA
0129         .                                  7540         1.00 
0130 
0131         In [15]: a.sel = None    # clear selection with None
0132 
0133         In [16]: a.his[:10]
0134         Out[16]: 
0135         .                                noname 
0136         .                               1000000         1.00 
0137            0               8ccccd        0.670         669843         [6 ] TO BT BT BT BT SA
0138            1                   4d        0.084          83950         [2 ] TO AB
0139            2              8cccc6d        0.045          45490         [7 ] TO SC BT BT BT BT SA
0140            3               4ccccd        0.029          28955         [6 ] TO BT BT BT BT AB
0141            4                 4ccd        0.023          23187         [4 ] TO BT BT AB
0142            5              8cccc5d        0.020          20239         [7 ] TO RE BT BT BT BT SA
0143            6              8cc6ccd        0.010          10214         [7 ] TO BT BT SC BT BT SA
0144            7              86ccccd        0.010          10176         [7 ] TO BT BT BT BT SC SA
0145            8              89ccccd        0.008           7540         [7 ] TO BT BT BT BT DR SA
0146            9             8cccc55d        0.006           5970         [8 ] TO RE RE BT BT BT BT SA
0147         .                               1000000         1.00 
0148 
0149 
0150     Using interactive low level *psel* selection, implemented via property setter which invokes _init_selection, to 
0151     select photons that have SA in the topslot ::
0152 
0153         In [7]: e1.psel = e1.seqhis & np.uint64(0xf000000000000000) == np.uint64(0x8000000000000000)
0154         [2016-11-07 11:44:22,464] p45629 {/Users/blyth/opticks/ana/evt.py:367} INFO - _init_selection nsel 2988 len(psel) 1000000  
0155 
0156         In [9]: e1.psel = e1.seqhis & 0xf000000000000000 == 0x8000000000000000         ## no need to specify the types
0157         [2016-11-07 11:46:25,879] p45629 {/Users/blyth/opticks/ana/evt.py:367} INFO - _init_selection nsel 2988 len(psel) 1000000  
0158 
0159         In [14]: e1.psel = ( e1.seqhis & ( 0xf << 4*15 )) >> 4*15 == 0x8     ## express with bitshifts
0160         [2016-11-07 11:57:06,754] p45629 {/Users/blyth/opticks/ana/evt.py:367} INFO - _init_selection nsel 2988 len(psel) 1000000  
0161 
0162 
0163         In [8]: e1.his
0164         Out[8]: 
0165         .                                noname 
0166         .                                  2988         1.00 
0167            0     8cccc6cccc9ccccd        0.189            566         [16] TO BT BT BT BT DR BT BT BT BT SC BT BT BT BT SA
0168            1     8cccccccc9cccc6d        0.122            365         [16] TO SC BT BT BT BT DR BT BT BT BT BT BT BT BT SA
0169            2     8cccc5cccc9ccccd        0.074            222         [16] TO BT BT BT BT DR BT BT BT BT RE BT BT BT BT SA
0170            3     8cccc6cccc6ccccd        0.052            155         [16] TO BT BT BT BT SC BT BT BT BT SC BT BT BT BT SA
0171            4     8cccccccc9cccc5d        0.050            150         [16] TO RE BT BT BT BT DR BT BT BT BT BT BT BT BT SA
0172            5     8cccccc6cc9ccccd        0.041            123         [16] TO BT BT BT BT DR BT BT SC BT BT BT BT BT BT SA
0173            6     8cc6cccccc9ccccd        0.040            119         [16] TO BT BT BT BT DR BT BT BT BT BT BT SC BT BT SA
0174            7     8cccccccc6cccc6d        0.038            115         [16] TO SC BT BT BT BT SC BT BT BT BT BT BT BT BT SA
0175            8     86cccccccc9ccccd        0.034            101         [16] TO BT BT BT BT DR BT BT BT BT BT BT BT BT SC SA
0176         ....
0177 
0178         In [11]: e1.mat
0179         Out[11]: 
0180         .                                noname 
0181         .                                  2988         1.00 
0182            0     3432311323443231        0.326            975         [16] Gd Ac LS Ac MO MO Ac LS Ac Gd Gd Ac LS Ac MO Ac
0183            1     3432313234432311        0.228            681         [16] Gd Gd Ac LS Ac MO MO Ac LS Ac Gd Ac LS Ac MO Ac
0184            2     3432313234443231        0.085            253         [16] Gd Ac LS Ac MO MO MO Ac LS Ac Gd Ac LS Ac MO Ac
0185            3     3443231323443231        0.080            239         [16] Gd Ac LS Ac MO MO Ac LS Ac Gd Ac LS Ac MO MO Ac
0186            4     3432231323443231        0.076            226         [16] Gd Ac LS Ac MO MO Ac LS Ac Gd Ac LS LS Ac MO Ac
0187            5     3432313223443231        0.073            219         [16] Gd Ac LS Ac MO MO Ac LS LS Ac Gd Ac LS Ac MO Ac
0188            6     3432313234432231        0.050            149         [16] Gd Ac LS LS Ac MO MO Ac LS Ac Gd Ac LS Ac MO Ac
0189            7     3432344323443231        0.026             79         [16] Gd Ac LS Ac MO MO Ac LS Ac MO MO Ac LS Ac MO Ac
0190            8     3432344323132231        0.016             47         [16] Gd Ac LS LS Ac Gd Ac LS Ac MO MO Ac LS Ac MO Ac
0191         ....
0192 
0193 
0194 
0195 
0196     Scales to avoiding trimming extremes ?
0197 
0198         In [63]: len(np.arange(0, 1+1+0xffff)[::128])
0199         Out[63]: 513
0200 
0201         In [64]: len(np.arange(0, 1+1+0xffff)[::256])
0202         Out[64]: 257
0203 
0204 
0205     """
0206     blib = blib 
0207     RPOST = {"X":X,"Y":Y,"Z":Z,"W":W,"T":T} 
0208     RPOL = {"A":X,"B":Y,"C":Z} 
0209 
0210     RQWN_BINSCALE = dict(X=1024,Y=1024,Z=1024,T=1024,R=1024,W=4,A=4,B=4,C=4) 
0211    
0212     # XYZT 
0213     #      position and time are short compressed, 
0214     #      (0x1 << 16)/1024  = 64
0215     #
0216     # ABCW 
0217     #      polarization and wavelength are char compressed 
0218     #      so are limited to 1 for primordial bins, or 2 or 4 
0219     #      (0x1 << 8)/4 = 64
0220     # 
0221 
0222 
0223     @classmethod
0224     def selection(cls, evt, seqs=[], not_=False, label=None, dbg=False):
0225         if label is None:
0226             label = evt.label + " sel %s " % (repr(seqs)) 
0227         pass
0228         sel = cls(tag=evt.tag, src=evt.src, det=evt.det, seqs=seqs, not_=not_ , label=label, maxrec=evt.maxrec, rec=evt.rec, dbg=dbg, args=evt.args )
0229         return sel 
0230 
0231     @classmethod
0232     def Load(cls, dirpath="/tmp/blyth/opticks/OKTest/evt/g4live/torch/1"):
0233         """
0234         TODO: create evt just from a dirpath 
0235         """
0236         pass
0237 
0238     def __init__(self, tag="1", src="natural", det="g4live", pfx=".", args=None, maxrec=10, rec=True, dbg=False, label=None, seqs=[], not_=False, nom="?"):
0239         log.debug("[ %s " % nom)
0240         self.nom = nom
0241         self._psel = None
0242         self._seqhis = None
0243         self._seqmat = None
0244         self._pflags = None
0245         self._labels = []
0246         self._irec = None
0247 
0248         self.align = None
0249         self.warn_empty = True
0250         self.dshape = ""
0251 
0252         log.debug("pfx:%s" % pfx)
0253 
0254         self.tag = str(tag)
0255         self.itag = int(self.tag)
0256         self.src = src
0257         self.det = det
0258         self.pfx = pfx 
0259         self.args = args
0260         self.maxrec = maxrec
0261         self.rec = rec
0262         self.dbg = dbg
0263 
0264         self.tagdir = tagdir_(det, src, tag, pfx=pfx)
0265         self.cn = "%s:%s:%s" % (str(self.tag), self.det, self.pfx)
0266 
0267         self.seqs = seqs
0268         self.not_ = not_
0269         self.flv = "seqhis"  # default for selections
0270 
0271         self.terse = args.terse
0272         self.msli = args.msli
0273 
0274         self.dbgseqhis = args.dbgseqhis
0275         self.dbgmskhis = args.dbgmskhis
0276         self.dbgseqmat = args.dbgseqmat
0277         self.dbgmskmat = args.dbgmskmat
0278         self.dbgzero = args.dbgzero 
0279         self.cmx = args.cmx
0280 
0281         log.debug( " seqs %s " % repr(seqs))
0282         log.debug(" dbgseqhis %x dbgmskhis %x dbgseqmat %x dbgmskmat %x " % (args.dbgseqhis, args.dbgmskhis, args.dbgseqmat, args.dbgmskmat ))
0283  
0284         self.rec = rec
0285         self.desc = odict()
0286 
0287         if label is None:
0288             label = "%s/%s/%s/%3s : %s" % (pfx, det, src, tag, ",".join(self.seqs)) 
0289         pass
0290         self.label = label
0291 
0292         if os.path.exists(self.tagdir):
0293             self.valid = True
0294             self.init() 
0295         else:
0296             self.valid = False  ## load failures signalled by setting False
0297             log.fatal("FAILED TO LOAD EVT : tagdir %s DOES NOT EXIST " % self.tagdir)
0298         pass
0299         log.debug("] %s " % nom)
0300 
0301 
0302     def init(self):
0303         ok = self.init_metadata()
0304         if not ok:
0305             log.warning("FAILED TO LOAD EVT %s " % self.label )
0306             return   
0307         pass
0308 
0309         self.init_types()
0310         self.init_gensteps()
0311         self.init_photons()
0312         self.init_hits()
0313 
0314         if self.rec:
0315             self.init_records()
0316             self.init_deluxe()
0317             self.init_boundary()
0318             self.init_sequence()
0319             #self.init_index()
0320             self.init_npoint()
0321             self.init_source()
0322 
0323             if len(self.seqs) == 0:
0324                 psel = None
0325             else:
0326                 psel = self.make_selection(self.seqs, self.not_)
0327             pass
0328             self.psel = psel      # psel property setter
0329         pass
0330         self.check_stamps()
0331         self.check_shapes()
0332  
0333 
0334 
0335     def init_types(self):
0336         """
0337         With test NCSG geometry get the GItemList/GMaterialLib.txt
0338         from the TestCSGPath dir 
0339         """
0340         log.debug("init_types")
0341         self.hismask = HisMask()
0342         self.histype = HisType()
0343 
0344         testcsgpath = self.metadata.TestCSGPath
0345         log.debug("testcsgpath %s " %  testcsgpath)
0346 
0347         if testcsgpath is not None:
0348             #reldir=os.path.abspath(os.path.join(testcsgpath,"GItemList"))  
0349             # ^^^^^^^^^^^^^^^^^^^^^^^^^ this assumes invoking dir is OPTICKS_EVENT_BASE wghen testcsgpath is relative
0350             if testcsgpath[0] == "/":
0351                 reldir = os.path.join(testcsgpath, "GItemList")
0352             else:
0353                 reldir = os.path.expandvars(os.path.join("$OPTICKS_EVENT_BASE", testcsgpath, "GItemList" ))
0354             pass
0355             log.debug("reldir %s " % reldir) 
0356             mattype = MatType(reldir=reldir)
0357         else:
0358             mattype = MatType()
0359         pass
0360         self.mattype = mattype
0361 
0362 
0363         log.debug("init_types DONE")
0364 
0365     def _get_flvtype(self):
0366         flv = self.flv
0367         if flv == "seqhis":
0368             _flvtype = self.histype
0369         elif flv == "seqmat":
0370             _flvtype = self.mattype
0371         elif flv == "pflags":
0372             _flvtype = self.hismask
0373         else:
0374             assert 0, flv
0375         return _flvtype
0376     flvtype = property(_get_flvtype)
0377 
0378     def _get_flvana(self):
0379         flv = self.flv
0380         if flv == "seqhis":
0381             _flvana = self.all_seqhis_ana
0382         elif flv == "seqmat":
0383             _flvana = self.all_seqmat_ana
0384         elif flv == "pflags":
0385             _flvana = self.all_pflags_ana
0386         else:
0387             assert 0, flv
0388         return _flvana
0389     flvana = property(_get_flvana)
0390 
0391 
0392     def _get_seqhis_labels(self):
0393         """
0394         HUH: duplicates seqhis_ls ??
0395         """
0396         af = self.histype 
0397         return "\n".join(map(lambda _:af.label(_), self.seqhis[:100] ))
0398     seqhis_labels = property(_get_seqhis_labels)
0399 
0400     def seqhis_splits(self, slot=1, dump=True ):
0401         """
0402         :param slot: nibble index
0403         :param dump: 
0404         :return uss: codes and counts of whatever is in the slot  
0405         """
0406         ss = nb_(self.seqhis, slot )
0407         uss = np.unique(ss, return_counts=True)
0408         tot = uss[1].sum()
0409         for i in range(len(uss[0])):
0410             code =  uss[0][i]
0411             label = self.histype.label(code)
0412             count = uss[1][i]
0413             frac  = float(count)/float(tot)
0414             if dump:
0415                 print( " 0x%x %2s %6d %10.3f   " % (code, label, count, frac ) ) 
0416             pass
0417         pass
0418         return uss
0419 
0420     def seqhis_counts(self, slot=1, labs="AB RE SC BT"):
0421         """
0422         :param slot: nibble index
0423         :param labs: string with space delimited list of 2 character history flag labels
0424         :param dump: 
0425         :return counts: seqhis counts for each requested label being in the slot   
0426         """
0427         ss = nb_(self.seqhis, slot )
0428         uss = np.unique(ss, return_counts=True)
0429         tot = uss[1].sum()
0430         
0431         codes = self.histype.code(labs.split())
0432         counts = np.zeros(len(codes), dtype=np.int32)
0433         for i in range(len(uss[0])):
0434             code =  uss[0][i]
0435             count = uss[1][i]
0436             if code in codes:
0437                 counts[codes.index(code)] = count 
0438             pass 
0439         pass
0440         return counts
0441 
0442 
0443 
0444     PhotonArrayStems = "ox rx dx bn ph so ps rs"
0445 
0446     @classmethod
0447     def IsPhotonArray(cls, stem):
0448         """ 
0449         ht is a bit different, 
0450         perhaps need a hits need to carry photon indices ? and event indices for ganging ?
0451         """ 
0452         return stem in cls.PhotonArrayStems
0453 
0454     def aload(self, stem, optional=False, g4only=False, okonly=False):
0455         msli = self.msli if self.IsPhotonArray(stem) else None
0456         a = A.load_(stem, self.src, self.tag, self.det, optional=optional, dbg=self.dbg, pfx=self.pfx, msli=msli, g4only=g4only, okonly=okonly ) 
0457         return a 
0458 
0459     def check_shapes(self):
0460         log.debug("[")
0461         file_photons = None
0462         loaded_photons = None
0463         load_slice = None 
0464 
0465         oshape_mismatch = []
0466         shape_mismatch = []
0467 
0468         stems = self.PhotonArrayStems.split()
0469         log.debug("stems : %s " % str(stems))
0470         for stem in stems:
0471 
0472             if stem == "so" or stem == "bn": continue 
0473             if not hasattr(self, stem): continue
0474             a = getattr(self, stem)
0475             if hasattr(a,"missing"): continue    
0476 
0477             log.debug("stem %s a.shape %s a.oshape %s  " % (stem, str(a.shape), str(a.oshape)))
0478 
0479             if file_photons is None:
0480                 file_photons = a.oshape[0] 
0481             else:
0482                 if a.oshape[0] != file_photons:
0483                     oshape_mismatch.append(stem)
0484                 pass
0485             pass  
0486 
0487             if loaded_photons is None:
0488                 loaded_photons = a.shape[0] 
0489             else:
0490                 if a.shape[0] != loaded_photons:
0491                     shape_mismatch.append(stem)
0492                 pass
0493             pass  
0494 
0495             if load_slice is None:
0496                 load_slice = a.msli
0497             else:
0498                 assert a.msli == load_slice
0499             pass  
0500             log.debug(" %s  %15s  %15s  %15s " % (stem, Num.String(a.oshape), _slice(a.msli), Num.String(a.shape)))
0501         pass
0502 
0503 
0504 
0505         if len(oshape_mismatch) > 0:
0506             log.fatal("oshape_mismatch : %s  file_photons %s " % (str(oshape_mismatch), file_photons))
0507         pass
0508         assert len(oshape_mismatch) == 0          
0509 
0510         if len(shape_mismatch) > 0:
0511             log.fatal("shape_mismatch : %s loaded_photons %s " % (str(shape_mismatch), loaded_photons))
0512         pass
0513         assert len(shape_mismatch) == 0          
0514 
0515         self.file_photons = file_photons
0516         self.loaded_photons = loaded_photons
0517         self.load_slice = load_slice
0518         self.dshape = self.dshape_() 
0519         log.debug( self.dshape )
0520         log.debug("]")
0521 
0522     dsli = property(lambda self:_slice(self.load_slice) if not self.load_slice is None else "-") # description of the load_slice 
0523 
0524     def dshape_(self):
0525         return " file_photons %s   load_slice %s   loaded_photons %s " % ( Num.String(self.file_photons), self.dsli, Num.String(self.loaded_photons) )
0526 
0527 
0528     def init_metadata(self):
0529         log.debug("init_metadata")
0530         metadata = Metadata(self.tagdir)
0531         log.debug("loaded metadata from %s " % self.tagdir)
0532         log.debug("metadata %s " % repr(metadata))
0533         self.metadata = metadata  
0534 
0535         fdom = self.aload("fdom")
0536         idom = self.aload("idom")
0537 
0538         if idom is None and fdom is None:
0539             log.warning("failed to load idom and fdom")
0540             self.fdom = None
0541             self.idom = None
0542             self.valid = False
0543             return False
0544 
0545         td = I.load_(self.src,self.tag,self.det, pfx=self.pfx, name="DeltaTime.ini", dbg=self.dbg)
0546         tdii = II.load_(self.src,self.tag,self.det, pfx=self.pfx, name="DeltaTime.ini", dbg=self.dbg)
0547 
0548         self.td = td
0549         self.tdii = tdii
0550 
0551         self.fdom = fdom
0552         self.fdomd = { 
0553                         'tmin':fdom[1,0,0],
0554                         'tmax':fdom[1,0,1],
0555                         'animtmax':fdom[1,0,2],
0556                         'xmin':fdom[0,0,0]-fdom[0,0,3],
0557                         'xmax':fdom[0,0,0]+fdom[0,0,3],
0558                         'ymin':fdom[0,0,1]-fdom[0,0,3],
0559                         'ymax':fdom[0,0,1]+fdom[0,0,3],
0560                         'zmin':fdom[0,0,2]-fdom[0,0,3],
0561                         'zmax':fdom[0,0,2]+fdom[0,0,3],
0562                      }  ## hmm epsilon enlarge for precision sneakers ?
0563                       
0564         self.idom = idom
0565         self.idomd = dict(maxrec=idom[0,0,3],maxbounce=idom[0,0,2],maxrng=idom[0,0,1])
0566 
0567         fdom.desc = "(metadata) 3*float4 domains of position, time, wavelength (used for compression)"
0568         self.desc['fdom'] = fdom.desc
0569         self.desc['idom'] = "(metadata) %s " % pdict_(self.idomd)
0570         return True         
0571 
0572 
0573 
0574     def init_gensteps(self):
0575         """
0576         """
0577         log.debug("init_gensteps")
0578         gs = self.aload("gs", optional=True) 
0579         self.gs = gs
0580         self.desc['gs'] = "(gensteps)"
0581 
0582 
0583     def make_pflags_ana(self, pflags, table_shortname):
0584         return SeqAna( pflags, self.hismask, cnames=[self.cn], dbgseq=self.dbgmskhis, dbgzero=self.dbgzero, cmx=self.cmx, table_shortname=table_shortname )
0585 
0586     def _get_pflags(self):
0587         if self._pflags is None:
0588             self._pflags = self.allpflags if self._psel is None else self.allpflags[self._psel]
0589         pass
0590         return self._pflags 
0591     pflags = property(_get_pflags)
0592 
0593 
0594 
0595 
0596     def init_photons(self):
0597         """
0598         #. c4 uses shape changing dtype splitting the 32 bits into 4*8 bits  
0599         """
0600         log.debug("init_photons")
0601         ox = self.aload("ox",optional=True) 
0602         self.ox = ox
0603         self.desc['ox'] = "(photons) final photon step %s " % ( "MISSING" if ox.missing else "" )
0604 
0605         if ox.missing:return 
0606 
0607         self.oxa = ox[:,:3,:]    # avoiding flags of last quad 
0608 
0609         self.check_ox_fdom()
0610 
0611         wl = ox[:,2,W] 
0612         c4 = ox[:,3,2].copy().view(dtype=[('x',np.uint8),('y',np.uint8),('z',np.uint8),('w',np.uint8)]).view(np.recarray)
0613         pos = ox[:,0,:3]
0614         r = np.sqrt(np.sum(pos*pos, axis=1))
0615 
0616         log.debug("ox shape %s " % str(ox.shape))
0617 
0618 
0619 
0620 
0621         post = ox[:,0]
0622         posr = np.sqrt(np.sum(post[:,:3]*post[:,:3], axis=1))
0623 
0624         self.wl = wl
0625  
0626         self.post = post 
0627         self.posr = posr
0628         self.dirw = ox[:,1]
0629         self.polw = ox[:,2]
0630         self.r = r 
0631 
0632         self.boundary = (( ox[:,3,0].view(np.uint32) & 0xffff0000 ) >> 16 ).view(np.int16)[0::2]  
0633         self.sensor   = (( ox[:,3,0].view(np.uint32) & 0x0000ffff ) >> 0 ) 
0634 
0635         allpflags = ox.view(np.uint32)[:,3,3]
0636         self.allpflags = allpflags 
0637 
0638         self.c4 = c4
0639 
0640 
0641         exclude_ecex = False
0642         if exclude_ecex: 
0643             ecex = self.hismask.code("EC|EX")  
0644             all_pflags_ana = self.make_pflags_ana( self.pflags & ~ecex , "all_pflags_ana" )  # SCRUB "EC|EX" **TEMPORARILY**
0645         else:
0646             all_pflags_ana = self.make_pflags_ana( self.pflags, "all_pflags_ana" )  
0647         pass 
0648 
0649         self.all_pflags_ana = all_pflags_ana
0650         self.pflags_ana = all_pflags_ana
0651 
0652         self.desc['wl'] = "(photons) wavelength"
0653         self.desc['post'] = "(photons) final photon step: position, time"
0654         self.desc['dirw'] = "(photons) final photon step: direction, weight "
0655         self.desc['polw'] = "(photons) final photon step: polarization, wavelength "
0656         self.desc['pflags'] = "(photons) final photon step: flags "
0657         self.desc['c4'] = "(photons) final photon step: dtype split uint8 view of ox flags"
0658 
0659 
0660     def check_ox_fdom(self):
0661         """
0662         Checking final photon positions with respect to the space and time domains
0663 
0664         * Observe precision sneakers beyond the domains.
0665         """
0666         chks = [ 
0667                   [ 'x', self.ox[:,0,0], self.fdomd["xmin"], self.fdomd["xmax"] ],
0668                   [ 'y', self.ox[:,0,1], self.fdomd["ymin"], self.fdomd["ymax"] ],
0669                   [ 'z', self.ox[:,0,2], self.fdomd["zmin"], self.fdomd["zmax"] ],
0670                   [ 't', self.ox[:,0,3], self.fdomd["tmin"], self.fdomd["tmax"] ],
0671                ]
0672         tot = 0 
0673         for l,q,mi,mx in chks:
0674             nmx = np.count_nonzero( q > mx ) 
0675             nmi = np.count_nonzero( q < mi ) 
0676             nto = len(q)
0677 
0678             tot += nmx
0679             tot += nmi
0680 
0681             fmt = "%5.3f"
0682             if nto > 0:
0683                 fmx = fmt % (float(nmx)/float(nto)) 
0684                 fmi = fmt % (float(nmi)/float(nto)) 
0685             else:
0686                 fmx = "-"
0687                 fmi = "-"
0688 
0689             msg = " %s : %7.3f %7.3f : tot %d over %d %s  under %d %s : mi %10.3f mx %10.3f  " % (l, mi, mx, nto, nmx,fmx,  nmi, fmi, q.min(), q.max() )
0690             if nmx == 0 and nmi == 0: 
0691                 log.debug(msg)
0692             else:
0693                 log.warning(msg)
0694             pass
0695         pass
0696         return tot
0697 
0698 
0699     def init_hits(self):
0700         log.debug("init_hits")
0701         ht = self.aload("ht",optional=True) 
0702         self.ht = ht
0703         self.desc['ht'] = "(hits) surface detect SD final photon steps"
0704 
0705 
0706         if ht.missing:return
0707 
0708         hwl = ht[:,2,W] 
0709         hc4 = ht[:,3,2].copy().view(dtype=[('x',np.uint8),('y',np.uint8),('z',np.uint8),('w',np.uint8)]).view(np.recarray)
0710 
0711         self.hwl = hwl
0712         self.hpost = ht[:,0] 
0713         self.hdirw = ht[:,1]
0714         self.hpolw = ht[:,2]
0715         self.hflags = ht.view(np.uint32)[:,3,3]
0716         self.hc4 = hc4
0717 
0718         #ipdb.set_trace() 
0719         self.hflags_ana = SeqAna( self.hflags, self.hismask, cnames=[self.cn], dbgseq=self.dbgmskhis, dbgzero=self.dbgzero, cmx=self.cmx, table_shortname="hflags_ana" )
0720  
0721         self.desc['hwl'] = "(hits) wavelength"
0722         self.desc['hpost'] = "(hits) final photon step: position, time"
0723         self.desc['hdirw'] = "(hits) final photon step: direction, weight "
0724         self.desc['hpolw'] = "(hits) final photon step: polarization, wavelength "
0725         self.desc['hflags'] = "(hits) final photon step: flags "
0726         self.desc['hc4'] = "(hits) final photon step: dtype split uint8 view of ox flags"
0727 
0728     htid = property(lambda self:self.ht.view(np.int32)[:,3,2]) ## photon_id of hits, requires optixrap/cu/generate.cu IDENTITY_DEBUG  
0729 
0730     def init_records(self):
0731         """
0732         """
0733         log.debug("init_records")
0734         
0735         rx = self.aload("rx",optional=True)
0736         self.rx = rx
0737         self.w0 = self.recwavelength(0)
0738         self.desc['rx'] = "(records) photon step records"
0739 
0740         if rx.missing:return 
0741        
0742         log.debug("rx shape %s " % str(rx.shape))
0743 
0744 
0745     def init_deluxe(self):
0746         """
0747         Hmm OpticksEvent is writing header only with disceptive shape for the +ve tag::
0748       
0749             epsilon:1 blyth$ xxd dx.npy
0750             00000000: 934e 554d 5059 0100 4600 7b27 6465 7363  .NUMPY..F.{'desc
0751             00000010: 7227 3a20 273c 6638 272c 2027 666f 7274  r': '<f8', 'fort
0752             00000020: 7261 6e5f 6f72 6465 7227 3a20 4661 6c73  ran_order': Fals
0753             00000030: 652c 2027 7368 6170 6527 3a20 2838 2c20  e, 'shape': (8, 
0754             00000040: 3130 2c20 322c 2034 292c 207d 2020 200a  10, 2, 4), }   .
0755 
0756         Workaround::
0757 
0758            epsilon:1 blyth$ mv dx.npy dx0.npy
0759 
0760         Have seen this before with other unfilled arrays. 
0761         """
0762         log.debug("init_deluxe")
0763         
0764         dx = self.aload("dx",optional=True, g4only=True)
0765         self.dx = dx
0766         self.desc['dx'] = "(deluxe) photon step records"
0767 
0768         if dx.missing:return 
0769         log.debug("dx shape %s " % str(dx.shape))
0770 
0771 
0772     def init_boundary(self):
0773         """
0774 
0775         """
0776         bn = self.aload("bn",optional=True, okonly=True)
0777         # suspect doing this looses the A type
0778         #self.bn = bn.reshape(-1,4).view(np.int8) if not bn.missing else bn
0779         self.bn = bn 
0780         self.desc['bn'] = "(boundary)  step records"
0781 
0782 
0783     def bnd(self):
0784         return self.bn.reshape(-1,4).view(np.int8)
0785 
0786     def init_source(self):
0787         """
0788         """
0789         log.debug("init_source")
0790         so = self.aload("so",optional=True)
0791         self.so = so
0792         self.desc['so'] = "(source) input CPU side emitconfig photons, or initial cerenkov/scintillation"
0793 
0794         if so.missing:return 
0795        
0796         log.debug("so shape %s " % str(so.shape))
0797 
0798 
0799     def pflags_where(self, abbrev="SC"):
0800         """
0801         pflags can be obtained from seqhis, by OR-ing 16 nibbles
0802         """
0803         co = self.hismask.code(abbrev)
0804         return np.where(self.pflags & co)[0]
0805 
0806     def pflags_subsample_where(self, jp, abbrev="SC"):
0807         """
0808         :param jp: subsample array of photon indices
0809         :param abbrev: step history abbeviation eg SC, AB, BR
0810         :return: indices from jp subsample which have seqhis-tories including the abbrev param
0811 
0812         :: 
0813 
0814             path = "$TMP/CRandomEngine_jump_photons.npy"
0815             jp, jp_paths = np_load(path)
0816             a_jpsc = ab.a.pflags_subsample_where(jp, "SC")
0817             b_jpsc = ab.b.pflags_subsample_where(jp, "SC")
0818 
0819         """
0820         co = self.hismask.code(abbrev) 
0821         jpsc = jp[np.where( seq2msk(self.seqhis[jp]) & co )]
0822         return jpsc
0823 
0824 
0825     def make_seqhis_ana(self, seqhis, table_shortname):
0826         return SeqAna( seqhis, self.histype, cnames=[self.cn], dbgseq=self.dbgseqhis, dbgmsk=self.dbgmskhis, dbgzero=self.dbgzero, cmx=self.cmx, table_shortname=table_shortname)
0827 
0828     def make_seqmat_ana(self, seqmat, table_shortname):
0829         return SeqAna( seqmat, self.mattype, cnames=[self.cn], dbgseq=self.dbgseqmat, dbgmsk=self.dbgmskmat, dbgzero=self.dbgzero, cmx=self.cmx, table_shortname=table_shortname)
0830 
0831     def make_seqhis_ls(self):
0832         return SeqList( self.seqhis, self.histype, slice(0,50) )
0833 
0834     def make_seqmat_ls(self):
0835         return SeqList( self.seqmat, self.mattype, slice(0,50) )
0836 
0837 
0838    
0839     def init_sequence(self):
0840         """
0841         Sequence values seqhis and seqmat for each photon::
0842 
0843             In [8]: a.ph.shape
0844             Out[8]: (100, 1, 2)
0845 
0846             In [9]: np.set_printoptions(formatter={'int':hex})
0847 
0848             In [10]: a.ph
0849             Out[10]: 
0850             A(torch,1,laser)-
0851             A([[[0x8ccccdL, 0x343231L]],
0852                [[0x8ccccdL, 0x343231L]],
0853                [[0x8cccc6dL, 0x3432311L]],
0854                [[0x8ccccdL, 0x343231L]],
0855                [[0x8ccccdL, 0x343231L]],
0856                [[0xbcbccccc6dL, 0x3333342311L]],
0857 
0858         """
0859         log.debug("init_sequence START")
0860 
0861         ph = self.aload("ph",optional=True)
0862         self.ph = ph
0863         self.desc['ph'] = "(records) photon history flag/material sequence"
0864 
0865         if ph.missing:
0866             log.debug(" ph missing ==> no history aka seqhis_ana  ")
0867             return 
0868 
0869         allseqhis = ph[:,0,0]
0870         allseqmat = ph[:,0,1]
0871 
0872         self.allseqhis = allseqhis
0873         self.allseqmat = allseqmat
0874 
0875         # full history without selection
0876         all_seqhis_ana = self.make_seqhis_ana(allseqhis, "all_seqhis_ana" )
0877         all_seqmat_ana = self.make_seqmat_ana(allseqmat, "all_seqmat_ana" )
0878 
0879         #self.seqhis = seqhis
0880         self.seqhis_ls = self.make_seqhis_ls()
0881         self.pflags2 = seq2msk(allseqhis)          # 16 seq nibbles OR-ed into mask 
0882 
0883 
0884         # see notes/issues/tds3ip_OK_pflags_mismatch_warning.rst
0885         exclude_ecex = True 
0886         if exclude_ecex:
0887             ecex = self.hismask.code("EC|EX")  
0888             pflags_without_ecex = self.pflags & ~ecex 
0889             pflags = pflags_without_ecex
0890         else:
0891             pflags = self.pflags
0892         pass  
0893 
0894         self.msk_mismatch = pflags != self.pflags2
0895         self.num_msk_mismatch = np.count_nonzero(self.msk_mismatch)
0896 
0897         if self.num_msk_mismatch == 0:
0898             log.debug("pflags2(=seq2msk(seqhis)) and pflags  match")
0899         else:
0900             log.info("pflags2(=seq2msk(seqhis)) and pflags  MISMATCH    num_msk_mismatch: %d " % self.num_msk_mismatch )
0901         pass
0902 
0903         #self.seqmat = seqmat
0904         self.seqmat_ls = self.make_seqmat_ls()
0905 
0906         useqhis = len(np.unique(allseqhis))
0907         useqmat = len(np.unique(allseqmat))
0908 
0909         if useqhis <= 1:
0910             log.warning("init_records %s finds too few (ph)seqhis uniques : %s : EMPTY HISTORY" % (self.label,useqhis) ) 
0911         if useqmat <= 1:
0912             log.warning("init_records %s finds too few (ph)seqmat uniques : %s : EMPTY HISTORY" % (self.label,useqmat) ) 
0913 
0914         self.all_seqhis_ana = all_seqhis_ana
0915         self.seqhis_ana = all_seqhis_ana
0916         ## when a selection is used seqhis_ana gets trumped by init_selection
0917 
0918         self.all_seqmat_ana = all_seqmat_ana
0919         self.seqmat_ana = all_seqmat_ana
0920 
0921 
0922         for imsk in range(1,10):
0923             msk = msk_(imsk) 
0924             table_shortname = "seqhis_ana_%d" % imsk
0925             setattr(self, table_shortname, SeqAna(allseqhis & msk, self.histype, cnames=[self.cn], table_shortname=table_shortname)) 
0926         pass
0927 
0928         log.debug("init_sequence DONE")
0929 
0930 
0931     def init_index(self):
0932         """
0933         Sequence indices for each photon::
0934  
0935             In [2]: a.ps.shape
0936             Out[2]: (100, 1, 4)
0937 
0938             In [1]: a.ps
0939             Out[1]: 
0940             A([[[ 1,  1,  0,  0]],
0941                [[ 1,  1,  0,  0]],
0942                [[ 3,  3,  0,  0]],
0943                [[ 1,  1,  0,  0]],
0944 
0945             In [6]: a.rs.shape      ## same information as a.ps duped by maxrec for record access
0946             Out[6]: (100, 10, 1, 4)
0947 
0948 
0949          The rs and ps recsel and phosel indices have fallen into disuse because its easy to derive 
0950          them from seqhis and seqmat. But with bigger photon counts it makes more sense to 
0951          use them again:: 
0952  
0953             In [9]: np.unique(a.ps[:,0,0])
0954             Out[9]: 
0955             A()sliced
0956             A([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], dtype=uint8)
0957 
0958             In [10]: np.unique(a.seqhis)
0959             Out[10]: 
0960             A()sliced
0961             A([        1229,         2157,         2237,        19405,        36045,       310221,       552141,       575181,       576461,       576621,      8833997,      9137357,      9202637,      9223117,
0962                     9223277,      9225917,    147549133,    147568333,    147569613,    147569773,    147572413,    147614925,   2361158861,  37777815245,  37777861837, 806308506573, 806308525773], dtype=uint64)
0963 
0964             In [11]: np.unique(a.seqhis).shape
0965             Out[11]: (27,)
0966 
0967 
0968             In [21]: np.all( a.rs[:,0,0,0] == a.ps[:,0,0] )
0969             Out[21]: 
0970             A(True)
0971 
0972 
0973         Actually a better reason for the disuse of phosel recsel is that they are not so useful for event comparison
0974         because the indices will not match in the category tail, explaining the below.
0975 
0976         * hence it is better to use the absolute seqhis history based approach.
0977 
0978         ::
0979 
0980             In [12]: np.where( a.phosel != b.phosel )
0981             Out[12]: (array([  595,  1230,  9041, 14510, 18921, 25113, 30272, 45629, 58189, 58609, 64663, 65850, 69653, 76467, 77962, 90322, 92353, 97887]),)
0982 
0983             In [13]: np.where( a.seqhis != b.seqhis )
0984             Out[13]: (array([], dtype=int64),)
0985 
0986         """
0987         log.debug("init_index START")
0988 
0989         assert 0, "no point : because the indices are only valid within single event, for comparisons use absolute seqhis"
0990 
0991         ps = self.aload("ps", optional=True) 
0992         rs = self.aload("rs", optional=True) 
0993 
0994         if not ps is None and not ps.missing:
0995             ups = len(np.unique(ps))
0996         else:
0997             ups = -1 
0998         pass
0999 
1000         if not rs is None and not rs.missing :
1001             urs = len(np.unique(rs))
1002         else: 
1003             urs = -1
1004         pass
1005 
1006         if ups <= 1 and not ps.missing:
1007             log.warning("init_index %s finds too few (ps)phosel uniques : %s" % (self.label,ups) ) 
1008         if urs <= 1 and not rs.missing:
1009             log.warning("init_index %s finds too few (rs)recsel uniques : %s" % (self.label,urs) ) 
1010 
1011         self.ps = ps
1012         self.rs = rs 
1013 
1014         if not ps is None:
1015             ps.desc = "(photons) phosel sequence frequency index lookups (uniques %d)"  % ups
1016             self.desc['ps'] = ps.desc
1017 
1018         if not rs is None:
1019             rs.desc = "(records) RAW recsel sequence frequency index lookups (uniques %d)"  % urs 
1020             self.desc['rs'] = rs.desc
1021 
1022         pass
1023         log.debug("init_index DONE")
1024 
1025     def _get_phosel(self):
1026         return self.ps[:,0,0] 
1027     phosel = property(_get_phosel)
1028 
1029     def _get_recsel(self):
1030         """matches phosel by construction""" 
1031         return self.rs[:,0,0,0] 
1032     recsel = property(_get_recsel)
1033 
1034 
1035     def init_npoint(self):
1036         """
1037         self.npo[i]
1038              number of points for photon index i 
1039 
1040         self.wpo[n]
1041              photon indices with for photons with n points (n < 16)
1042 
1043         The wpo and npo are used by rpostn and rposti 
1044 
1045         See ana/nibble.py for exploration of various bit twiddling approaches
1046         to counting occupied nibbles
1047 
1048         Bit shifting the seqhis to the right by 4,8,12,16,... bits
1049         and looking for surviving 1s gives all entries with that number of record points
1050 
1051         """
1052         log.debug(" ( init_npoint")
1053 
1054         if self.ph.missing:
1055             log.info("ph.missing so no seqhis ")  # hmm can do limited analysis on the hits though 
1056             return 
1057         pass 
1058 
1059         x = self.seqhis
1060 
1061         qpo = odict() 
1062         wpo = odict() 
1063         npo = np.zeros(len(x), dtype=np.int32)
1064 
1065         for i in range(16):
1066             qpo[i] = np.where( x >> (4*i) != 0 )
1067             npo[qpo[i]] = i+1
1068             wpo[i] = np.where( (x & ~msk[i] == 0) & (x & NIB[i] != 0) )
1069         pass
1070         self.wpo = wpo
1071         self.npo = npo
1072         log.debug(" ) init_npoint")
1073 
1074 
1075     his = property(lambda self:self.seqhis_ana.table)
1076     mat = property(lambda self:self.seqmat_ana.table)
1077     flg = property(lambda self:self.pflags_ana.table)
1078 
1079     ahis = property(lambda self:self.all_seqhis_ana.table)
1080     amat = property(lambda self:self.all_seqmat_ana.table)
1081     aflg = property(lambda self:self.all_pflags_ana.table)
1082 
1083     def make_labels(self, sel):
1084         """
1085         :param sel: selection choice argument 
1086 
1087         Sel can be of various types:
1088 
1089         *slice*
1090             picks seqmat or seqhis labels depending on the flv member
1091         *list*
1092             with elements which can be 
1093  
1094             *hexint* 
1095                 eg 0x8ccd 
1096 
1097             *hexstring(without 0x)* 
1098                  eg "8ccd"
1099 
1100             *string* 
1101                  preformed labels "TO BT BT AB"  
1102 
1103         """ 
1104         if type(sel) is slice:
1105             if self.flv == "seqhis":
1106                 labels = self.ahis.labels[sel] 
1107             elif self.flv == "seqmat":
1108                 labels = self.amat.labels[sel] 
1109             else:
1110                 assert 0, flv
1111             pass
1112         elif type(sel) is list:
1113             labels =list( map(lambda _:self.flvtype.label(_), sel ))
1114         elif type(sel) is str or type(sel) is int or type(sel) is np.uint64:
1115            labels = [self.flvtype.label(sel)]
1116         else:
1117             log.fatal("unhandled selection type %s %s " % (sel, type(sel)))
1118             assert 0
1119 
1120         log.debug("%s.make_labels %s ->  %s " % (self.nom, repr(sel), repr(labels)))
1121         return labels
1122  
1123     def make_psel_startswith(self, lab):
1124         flvana = self.flvana 
1125         psel = flvana.seq_startswith(lab)
1126         return psel
1127 
1128     def make_psel_or(self, labs):
1129         flvana = self.flvana 
1130         psel = flvana.seq_or(labs)
1131         return psel
1132 
1133     def make_psel_any(self, lab):
1134         flvana = self.flvana 
1135         psel = flvana.seq_any(lab)
1136         return psel
1137 
1138 
1139     def make_selection_(self, labels):
1140         """
1141         :param labels: usually seqhis line label strings such at "TO BT BT SA"
1142         :return psel: boolean photon selection array 
1143         """
1144         if not self.rec or len(labels) == 0:
1145             log.info("skip make_selection_ as no labels")
1146             return None
1147    
1148         log.debug("%s.make_selection_ labels %s " % (self.nom,repr(labels)))
1149         self._labels = labels
1150 
1151         if len(labels) == 1 and labels[0][-3:] == ' ..':
1152             log.debug("make_selection_ wildcard startswith %s " % labels[0] ) 
1153             lab = labels[0][:-3]  # exclude the " .."
1154             psel = self.make_psel_startswith(lab)
1155         elif len(labels) == 1 and labels[0][0] == '*' and labels[0][-1] == '*' :
1156             lab = labels[0][1:-1] 
1157             psel = self.make_psel_any(lab)
1158         elif len(labels) == 1 and labels[0] == 'PFLAGS_DEBUG':
1159             psel = self.pflags2 != self.pflags
1160         elif len(labels) == 1 and labels[0] == 'ALIGN':
1161             psel = None
1162         else:
1163             psel = self.make_psel_or(labels)
1164         pass
1165         if self._align is not None and psel is not None:
1166             psel = np.logical_and( psel, self._align ) 
1167         elif self._align is not None:
1168             psel = self._align  
1169         pass
1170         return psel
1171 
1172 
1173     def _set_align(self, align):
1174         self._align = align 
1175     def _get_align(self):  
1176         return self._align
1177     align = property(_get_align , _set_align)
1178 
1179 
1180     def _get_label0(self):
1181         """
1182         hmm _labels is ["ALIGN"] yielding lab0 "ALIGN"
1183         """
1184         nlab = len(self._labels) 
1185         if nlab == 1:
1186             lab0 = self._labels[0]
1187         else:
1188             lab0 = None
1189         return lab0
1190     label0 = property(_get_label0)
1191    
1192 
1193     def iflg(self, flg):
1194        """
1195        :param flg: eg SC
1196        :return iflg: zero based index of the flg within the label
1197 
1198        For example iflg('SC') with label 'TO BT BT SC ..' would return 3
1199        if a non-single line selection were active or the flg did not 
1200        appear None is returned.
1201 
1202        iflg('BT') returns 1, in index of the first occurrence 
1203 
1204        """ 
1205        lab0 = self.label0
1206        if lab0 is None:
1207            log.fatal("Evt.index0 requires single line selection active, eg sel = slice(0,1) " )
1208            return None
1209        pass
1210        flgs = lab0.split()
1211        if flgs.count(flg) == 0:
1212            log.fatal("Evt.index0 expects label0 %s containg flg %s " % (lab0, flg)) 
1213            return None
1214        pass
1215        iflg = flgs.index(flg) 
1216        return iflg
1217 
1218  
1219     def _get_nrec(self):
1220         """
1221         :return: number of records
1222 
1223         with a single label selection such as "TO BT AB" nrec would return  3
1224         If there is no single label selection return -1  
1225 
1226         See init_npoint for a much more efficient way of doing this for all 
1227         photons by counting occupied nibbles in seqhis
1228 
1229         Hmm label0 ALIGN, yields nrec 1
1230         """ 
1231         lab0 = self.label0
1232         if lab0 is None:
1233             return -1
1234         elab = lab0.split()
1235         return len(elab)
1236     nrec = property(_get_nrec)
1237 
1238     def _get_alabels(self):
1239         """
1240         :return alabels: all labels of current flv 
1241 
1242         NB cannot directly use with AB comparisons as the labels 
1243         will be somewhat different for each evt 
1244         """
1245         if self.flv == "seqhis":
1246             alabels = self.ahis.labels
1247         elif self.flv == "seqmat":
1248             alabels = self.amat.labels
1249         else:
1250             alabels = []
1251         pass
1252         return alabels
1253     alabels = property(_get_alabels)
1254 
1255 
1256     def nrecs(self, start=0, stop=None, step=1):
1257         sli = slice(start, stop, step)
1258         labels = self.alabels[sli] 
1259         nrs = np.zeros(len(labels), dtype=np.int32) 
1260         for ilab, lab in enumerate(labels):
1261             nrs[ilab] = len(lab.split())
1262         pass
1263         return nrs
1264 
1265     def totrec(self, start=0, stop=None, step=1):
1266         nrs = self.nrecs(start, stop, step)
1267         return int(nrs.sum())
1268 
1269 
1270     def _get_recs(self):
1271         """
1272         Restriction is so the shapes are all the same
1273         """
1274         nr = self.nrec
1275         if nr == -1:
1276             log.warning("recs slicing only works when a single label selection is active ")
1277             return None
1278         pass
1279         return slice(0,nr)
1280     recs = property(_get_recs)
1281 
1282     def make_selection(self, sel, not_):
1283         """
1284         :param sel: selection labels 
1285         :param not_: inverts selection
1286         :return psel: boolean photon selection array 
1287         """
1288         if sel is None:
1289             return None
1290 
1291         labels = self.make_labels(sel)
1292         psel = self.make_selection_(labels)
1293         if not_:
1294             psel = np.logical_not(psel)
1295         pass
1296         return psel
1297 
1298 
1299     def _reset_selection(self):
1300         if hasattr(self, 'ox_'):
1301             log.debug("_reset_selection with psel None : resetting selection to original ")
1302             self.ox = self.ox_
1303             self.oxa = self.oxa_
1304             self.c4 = self.c4_
1305             self.wl = self.wl_
1306             self.rx = self.rx_
1307             self.so = self.so_
1308             self.seqhis_ana = self.all_seqhis_ana  
1309             self.seqmat_ana = self.all_seqmat_ana   
1310             self.pflags_ana = self.all_pflags_ana
1311             #self.seqhis_ana = self.make_seqhis_ana( self.seqhis )   
1312             #self.seqmat_ana = self.make_seqmat_ana( self.seqmat )   
1313             #self.pflags_ana = self.make_pflags_ana( self.pflags ) 
1314             self.nsel = len(self.ox_)
1315         else:
1316             log.warning("_reset_selection with psel None : no prior selection, ignoring ")
1317         pass
1318 
1319 
1320     def _get_seqhis(self):
1321         if self._seqhis is None:
1322             self._seqhis = self.allseqhis[self._psel] if not self._psel is None else self.allseqhis
1323         return self._seqhis
1324     seqhis = property(_get_seqhis) 
1325 
1326     def _get_seqmat(self):
1327         if self._seqmat is None:
1328             self._seqmat = self.allseqmat[self._psel] if not self._psel is None else self.allseqmat
1329         return self._seqmat
1330     seqmat = property(_get_seqmat) 
1331 
1332 
1333     def _get_seqhis_ana(self):
1334         if self._seqhis_ana is None:
1335             self._seqhis_ana = self.make_seqhis_ana(self.seqhis)
1336         return self._seqhis_ana
1337     def _set_seqhis_ana(self, sa ):
1338         self._seqhis_ana = sa
1339     seqhis_ana = property( _get_seqhis_ana, _set_seqhis_ana ) 
1340 
1341 
1342     def _init_selection(self, psel):
1343         """
1344         :param psel: photon length boolean selection array, make it with make_selection or directly with numpy 
1345 
1346         * applies boolean selection array to all arrays, such that all access eg rpost() honours the selections
1347         * the low level way to invoke this is by setting the psel property
1348 
1349         TODO:
1350 
1351         * try to make this more lazy, perhaps seqhis_ana etc can become properties
1352           as this code gets called a lot.  For example the deviations apply 
1353           line by line selections 
1354 
1355         """
1356         #if self.ox.missing:
1357         #     return
1358         #pass
1359 
1360         # for first _init_selection hold on to the originals
1361         if self._psel is None:
1362             self.ox_ = self.ox
1363             self.oxa_ = self.oxa
1364             self.c4_ = self.c4
1365             self.wl_ = self.wl
1366             self.rx_ = self.rx
1367             self.so_ = self.so
1368             self.dx_ = self.dx
1369             self.bn_ = self.bn
1370         pass
1371         if psel is None: 
1372             self._reset_selection()
1373             return 
1374         pass   
1375         log.debug("psel %s " % repr(psel))
1376 
1377         self._psel = psel 
1378         nsel = len(psel[psel == True])
1379         if nsel == 0:
1380             if self.warn_empty:
1381                 log.warning("_init_selection EMPTY nsel %s len(psel) %s " % (nsel, len(psel)))
1382             pass
1383         else:
1384             log.debug("_init_selection nsel %s len(psel) %s  " % (nsel, len(psel)))
1385         pass 
1386         self.nsel = nsel 
1387 
1388         ## always basing new selection of the originals
1389         self.ox = self.ox_[psel]
1390         self.oxa = self.oxa_[psel]
1391         self.c4 = self.c4_[psel]
1392         self.wl = self.wl_[psel]
1393         self.rx = self.rx_[psel]
1394 
1395         if not self.dx_.missing:
1396             self.dx = self.dx_[psel]
1397         pass
1398         if not self.so_.missing and len(self.so_)>0:
1399             self.so = self.so_[psel]
1400         pass
1401         if not self.bn_.missing and len(self.bn_)>0:
1402             self.bn = self.bn_[psel]
1403         pass
1404 
1405         self.seqhis_ana = self.make_seqhis_ana( self.seqhis[psel], "seqhis_ana" )   # sequence history with selection applied
1406         self.seqmat_ana = self.make_seqmat_ana( self.seqmat[psel], "seqmat_ana" )   
1407         self.pflags_ana = self.make_pflags_ana( self.pflags[psel], "pflags_ana" )
1408 
1409 
1410     def _get_utail(self):
1411         """
1412         p.flags.f.y  is stomped with extra random when using --utaildebug 
1413         """
1414         return self.ox[:,3,1]    
1415     utail = property(_get_utail) 
1416 
1417 
1418     def _get_reclab(self):
1419         """
1420         Sequence label with single record highlighted with a bracket 
1421         eg  TO BT [BR] BR BT SA 
1422 
1423         """
1424         nlab = len(self._labels) 
1425         if nlab == 1 and self._irec > -1: 
1426             lab = self._labels[0]
1427             elab= lab.split()
1428             if self._irec < len(elab):
1429                 elab[self._irec] = "[%s]" % elab[self._irec]
1430             pass
1431             lab = " ".join(elab) 
1432         else:
1433             lab = ",".join(self._labels) 
1434         pass
1435         return lab 
1436     reclab = property(_get_reclab)
1437 
1438 
1439     # *irec* is convenience pointer the current record of interest 
1440     def _get_irec(self):
1441         return self._irec
1442     def _set_irec(self, irec):
1443         ## hmm could convert a string "SC" into the first occurence integer ?
1444 
1445         nr = self.nrec
1446         if nr > -1 and irec < 0:
1447             irec += nr
1448 
1449         self._irec = irec
1450     irec = property(_get_irec, _set_irec)
1451  
1452 
1453     # *psel* provides low level selection control via  boolean array 
1454     def _get_psel(self):
1455         return self._psel
1456     def _set_psel(self, psel):
1457         self._init_selection(psel)
1458     psel = property(_get_psel, _set_psel)
1459 
1460     def _parse_sel(self, arg):
1461         """
1462         Note that use of square backet record selection will
1463         cause a jump to seqhis flv selections
1464         """
1465         sel = arg 
1466         if not type(arg) is list and arg.find("[") > -1:
1467             ctx = Ctx.reclab2ctx_(arg)
1468             log.debug("_parse_sel with reclab converted arg %s into ctx %r " % (arg, ctx)) 
1469             sel = ctx["seq0"]
1470             irec = ctx["irec"]
1471             self.flv = "seqhis"
1472             self.irec = irec   
1473         pass
1474 
1475         #if arg.find("|") > -1:
1476         #    log.warning("_parse_sel arg %s causes flv switch to pflags " % arg)
1477         #    self.flv = "pflags"
1478         #    sel = arg
1479         pass
1480         return sel
1481 
1482     # *sel* provides high level selection control using slices, labels, hexint etc
1483     def _get_sel(self):
1484         return self._sel
1485     def _set_sel(self, arg):
1486         log.debug("Evt._set_sel %s " % repr(arg))
1487 
1488         if arg is None:
1489             sel = None
1490         else:
1491             sel = self._parse_sel(arg)
1492         pass
1493         self._sel = sel
1494 
1495         psel = self.make_selection(sel, False)
1496         self._init_selection(psel)
1497     sel = property(_get_sel, _set_sel)
1498      
1499 
1500 
1501     ## avoid complexities of auto detection, by providing explicit selection interface props
1502     def _set_selmat(self, arg):
1503         self.flv = "seqmat"
1504         self.sel = arg 
1505     selmat = property(_get_sel, _set_selmat)
1506 
1507     def _set_selhis(self, arg):
1508         self.flv = "seqhis"
1509         self.sel = arg 
1510     selhis = property(_get_sel, _set_selhis)
1511 
1512     def _set_selflg(self, arg):
1513         self.flv = "pflags"
1514         self.sel = arg 
1515     selflg = property(_get_sel, _set_selflg)
1516 
1517 
1518     def _get_where(self):
1519         """
1520         returns psel record positions of current selection
1521         """ 
1522         return np.where(self.psel)[0] 
1523     where = property(_get_where)
1524  
1525     def psel_dindex_(self, limit=None, reverse=False):
1526         """
1527         Return dindex option string allowing debug dumping during OKG4 running,
1528         see for example tconcentric-tt-pflags  
1529         """ 
1530         a = np.where(self.psel)[0]
1531         if reverse:
1532             a = a[::-1] 
1533         return a[:limit]
1534 
1535     def psel_dindex(self, limit=None, reverse=False):
1536         return "--dindex=%s" % ",".join(map(str,self.psel_dindex_(limit, reverse))) 
1537 
1538     def select_(self, label="TO RE BT BT BT BT SA"):
1539         """
1540         :param label: seqhis or seqmat label
1541         :return boolean selection array:
1542         """
1543         if label == "BAD_PFLAGS":
1544             select = self.pflags2 != self.pflags
1545         else:
1546             if label[0:2] in "TO CK SI GN NL".split():
1547                 code = self.histype.code(label)
1548                 select = self.seqhis == code
1549             else:
1550                 code = self.mattype.code(label)
1551                 select = self.seqmat == code
1552             pass
1553         pass
1554         return select 
1555 
1556     def dindex_(self, label="TO RE BT BT BT BT SA", limit=None, reverse=False):
1557         """
1558         :param label: seqhis or seqmat label
1559         :param limit:
1560         :return array: list of photon record_id that match the seqhis label 
1561 
1562         ::  
1563 
1564             In [36]: ab.a.dindex("TO BT AB")
1565             Out[36]: '--dindex=2084,4074,15299,20870,25748,26317,43525,51563,57355,61602'
1566 
1567             In [37]: ab.b.dindex("TO BT AB")
1568             Out[37]: '--dindex=2084,4074,15299,20870,25748,26317,43525,51563,57355,61602'
1569 
1570         """
1571         select = self.select_(label)
1572         a = np.where(select)[0]
1573         if reverse:
1574             a = a[::-1] 
1575         return a[:limit]
1576 
1577     def dindex(self, label="TO RE BT BT BT BT SA", limit=10, reverse=False ):
1578         """
1579         :param label: seqhis label
1580         :param limit:
1581         :return string: dindex option string with record_id of photons with the selected history 
1582 
1583         Find record_id of photons with particular histories::
1584 
1585             In [27]: e1.dindex("TO AB")
1586             Out[27]: '--dindex=13,14,24,26,28,34,53,83,84,98'
1587 
1588             In [28]: e1.dindex("TO SC AB")
1589             Out[28]: '--dindex=496,609,698,926,1300,1346,1356,1633,1637,2376'
1590 
1591             In [29]: e1.dindex("TO RE SC AB")
1592             Out[29]: '--dindex=1472,10272,12785,17200,22028,24184,31503,32334,43509,44892'
1593 
1594             In [31]: e1.dindex("TO RE BT BT BT BT SA")
1595             Out[31]: '--dindex=63,115,124,200,225,270,307,338,342,423'
1596 
1597             In [36]: e1.dindex("BAD_PFLAGS")
1598             Out[36]: '--dindex=3352,12902,22877,23065,41882,60653,68073,69957,93373,114425'
1599 
1600             In [37]: e1.dindex("BAD_PFLAGS",reverse=True)
1601             Out[37]: '--dindex=994454,978573,976708,967547,961725,929984,929891,925473,919938,917897'
1602 
1603         """
1604         return "--dindex=%s" % ",".join(map(str,self.dindex_(label, limit, reverse))) 
1605 
1606 
1607 
1608  
1609     x = property(lambda self:self.ox[:,0,0])
1610     y = property(lambda self:self.ox[:,0,1])
1611     z = property(lambda self:self.ox[:,0,2])
1612     t = property(lambda self:self.ox[:,0,3])
1613 
1614 
1615     description = property(lambda self:"\n".join(["%7s : %12s : %12s : %s " % (k, Num.OrigShape(getattr(self,k)),  Num.String(getattr(self,k).shape), label) for k,label in self.desc.items()]))
1616     paths = property(lambda self:"\n".join(["%5s : %s " % (k, repr(getattr(getattr(self,k),'path','-'))) for k,label in self.desc.items()]))
1617 
1618     def _path(self):
1619         if not hasattr(self, 'fdom'):
1620              return None
1621         elif self.fdom is None:
1622              return None
1623         else:
1624              return self.fdom.path
1625     path = property(_path)
1626     stamp = property(lambda self:stamp_(self.path) if not self.path is None else "INVALID")
1627 
1628 
1629     def check_stamps(self, names="fdom idom ox rx ht"):
1630         sst = set()
1631 
1632         lines = []
1633         for name in names.split():
1634             a = getattr(self, name, None)
1635             if a is None:continue
1636             p = getattr(a,"path",None)
1637 
1638             #fmt = "%Y%m%d-%H%M"
1639             fmt = "%Y%m%d-%H"
1640             t = stamp_(p, fmt)
1641             if t is not None:   # ht are often missing 
1642                 sst.add(t)
1643             pass
1644             lines.append("%10s  %s  %s " % (name, p, t ))
1645         pass
1646         nstamp = len(sst)
1647         if nstamp > 1:
1648             log.warning("MIXED TIMESTAMP EVENT DETECTED %s " % repr(sst) )
1649             print("\n".join(lines))
1650         pass 
1651         return nstamp
1652 
1653 
1654     def _brief(self):
1655         if self.valid:
1656              return "%s %s %s %s (%s)" % (self.label, self.stamp, pdict_(self.idomd), self.path, self.metadata.Note)
1657         else:
1658              return "%s %s" % (self.label, "EVT LOAD FAILED")
1659 
1660     brief = property(_brief)
1661     summary = property(lambda self:"Evt(%3s,\"%s\",\"%s\",pfx=\"%s\", seqs=\"%s\", msli=\"%s\" ) %s " % 
1662             (self.tag, self.src, self.det,self.pfx, repr(self.seqs), _slice(self.msli), self.stamp ))
1663 
1664     def __repr__(self):
1665         if self.terse:
1666             elem = [self.summary, self.tagdir, self.dshape]
1667         else:
1668             elem = [self.summary, self.tagdir, self.dshape, self.description]
1669         pass
1670         return "\n".join(elem)
1671 
1672     def msize(self):
1673         return float(self.ox.shape[0])/1e6
1674 
1675     def unique_wavelength(self):
1676         uwl = np.unique(self.wl)
1677         assert len(uwl) == 1 
1678         return uwl[0]
1679 
1680     def present_table(self, analist,sli=slice(None)):
1681         """
1682         TODO: make the table directly sliceable
1683         """
1684         for ana_ in analist:
1685             ana = getattr(self, ana_, None) 
1686             if ana:
1687                 log.debug("history_table %s " % ana_ )
1688                 ana.table.title = ana_ 
1689                 ana.table.sli = sli 
1690                 print(ana.table)
1691             else:
1692                 log.debug("%s noattr " % ana_ )
1693         pass
1694 
1695     def history_table(self, sli=slice(None)):
1696         self.present_table( 'seqhis_ana seqmat_ana hflags_ana pflags_ana'.split(), sli)
1697 
1698     def material_table(self, sli=slice(None)):
1699         self.present_table( 'seqmat_ana'.split(), sli)
1700 
1701 
1702 
1703 
1704 
1705     @classmethod
1706     def compare_ana(cls, a, b, ana_ , lmx=20, c2max=None, cf=True, zero=False, cmx=0, pr=False, ordering="max", shortname="noshortname?" ):
1707         """
1708         :param a: evt A
1709         :param b: evt B
1710         :param ana_: string name eg "seqhis_ana", "pflags_ana"
1711 
1712         Comparison of history tables, tables are count_unique_sorted summaries of the full sequence arrays 
1713         """
1714         if not (a.valid and b.valid):
1715             log.fatal("need two valid events to compare ")
1716             sys.exit(1)
1717         pass
1718 
1719         a_ana = getattr(a, ana_, None)   # opticks.ana.seq.SeqAna
1720         b_ana = getattr(b, ana_, None)   # opticks.ana.seq.SeqAna
1721         log.debug("a_ana type %s " % type(a_ana)) 
1722         log.debug("b_ana type %s " % type(b_ana))
1723 
1724 
1725         if a_ana is None:
1726             log.warning("missing a_ana %s " % ana_ )  
1727             return None
1728         if b_ana is None:
1729             log.warning("missing b_ana  %s " % ana_ )  
1730             return None
1731              
1732         a_tab = a_ana.table
1733         b_tab = b_ana.table
1734         c_tab = None
1735 
1736         if cf:
1737             c_tab = a_tab.compare(b_tab, ordering=ordering, shortname=shortname)   # see seq.py SeqTable.compare 
1738             c_tab.title = ana_
1739 
1740             if len(list(c_tab.lines)) > lmx:
1741                 c_tab.sli = slice(0,lmx)
1742             pass
1743             if pr:
1744                 print(c_tab)
1745             if c2max is not None:
1746                 assert c_tab.c2p < c2max, "c2p deviation for table %s c_tab.c2p %s >= c2max %s " % ( ana_, c_tab.c2p, c2max )
1747             pass
1748         else:
1749             a_tab.title = "A:%s " % ana_
1750             if len(list(a_tab.lines)) > lmx:
1751                 a_tab.sli = slice(0,lmx)
1752             if pr:
1753                 print(a_tab)
1754             pass
1755             b_tab.title = "B:%s " % ana_
1756             if len(list(b_tab.lines)) > lmx:
1757                 b_tab.sli = slice(0,lmx)
1758             if pr:
1759                 print(b_tab)
1760         pass
1761         return c_tab
1762 
1763 
1764     @classmethod
1765     def compare_table(cls, a, b, analist='seqhis_ana seqmat_ana'.split(), lmx=20, c2max=None, cf=True, zero=False, cmx=0, pr=False):
1766         cft = {}
1767         for ana_ in analist:
1768             c_tab = cls.compare_ana( a, b, ana_ , lmx=lmx, c2max=c2max, cf=cf, zero=zero, cmx=cmx, pr=pr)
1769             cft[ana_] = c_tab 
1770         pass
1771         return cft
1772 
1773 
1774 
1775     def material_table_old(self):
1776         seqmat = self.seqmat
1777         cu = count_unique(seqmat)
1778         ## TODO: fix this  
1779         seqmat_table(cu)
1780         tot = cu[:,1].astype(np.int32).sum()
1781         print("tot:%d" % tot)
1782         return cu
1783 
1784     def flags_table_old(self):
1785         flags = self.flags
1786         cu = count_unique(flags)
1787         gflags_table(cu)
1788         tot = cu[:,1].astype(np.int32).sum()
1789         print("tot:%d" % tot)
1790         brsa = maskflags_int("BR|SA|TORCH")
1791         return cu
1792 
1793     def seqhis_or_not(self, args):
1794         return np.logical_not(self.seqhis_or(args))    
1795 
1796 
1797     def rw(self):
1798         recs = self.recs
1799         if recs is None:
1800              return None  
1801 
1802         return self.recwavelength(recs)
1803 
1804     def recwavelength(self, recs):
1805         pzwl = self.rx[:,recs,1,1]
1806         nwavelength = (pzwl & np.uint16(0xFF00)) >> 8
1807         return self.w_decompress(nwavelength)
1808 
1809     def wbins(self):
1810         """
1811         ::
1812 
1813             In [23]: b.wbins()
1814             Out[23]: 
1815             array([  60.    ,   62.9804,   65.9608, ... 814.0392,  817.0196,  820.    ], dtype=float32)
1816 
1817             In [24]: b.wbins().shape
1818             Out[24]: (256,)
1819 
1820         """
1821         nwavelength = np.arange(0,0xff+1)   ## hmm could be off by one here
1822         return self.w_decompress(nwavelength)
1823 
1824     def w_decompress(self, nwavelength):
1825         boundary_domain = self.fdom[2,0]
1826         return nwavelength.astype(np.float32)*boundary_domain[W]/255.0 + boundary_domain[X]
1827 
1828     def rpol(self):
1829         recs = self.recs
1830         if recs is None:
1831              return None  
1832         return self.rpolw_(recs)[:,:,:3]
1833 
1834     def rpol_(self, fr):
1835         return self.rpolw_(fr)[:,:3]
1836 
1837     def _get_rpola(self):
1838         return self.rpol_(slice(None))
1839     rpola = property(_get_rpola)
1840 
1841 
1842     def rpolw_(self, recs):
1843         """
1844         Unlike rpol_ this works with irec slices, 
1845         BUT note that the wavelength returned in 4th column is 
1846         not decompressed correctly.
1847         Due to shape shifting it is not easy to remove
1848         """
1849         return self.rx[:,recs,1,0:2].copy().view(np.uint8).astype(np.float32)/127.-1.
1850 
1851     def rpol_old_(self, recs):
1852         """
1853         TODO: rearrange to go direct from recs to the 
1854               result without resorting to new allocation
1855               (maybe by viewing as recarray of appropriate types)
1856 
1857               This then allows irec to be a slice
1858 
1859         In [80]: evt.rx[:,0,1,0:2].copy().view(np.uint8).astype(np.float32)/127.-1.
1860         Out[80]: 
1861         A([[-0.378,  0.929,  0.   , -0.157],
1862                [-0.244,  0.969,  0.   , -0.157],
1863                [-1.   ,  0.   ,  0.   , -0.157],
1864                ..., 
1865                [-0.992,  0.094,  0.   , -0.157],
1866                [-0.78 , -0.63 ,  0.   , -0.157],
1867                [ 0.969, -0.244,  0.   , -0.157]], dtype=float32)
1868 
1869         In [81]: evt.rpol_(0)
1870         Out[81]: 
1871         array([[-0.378,  0.929,  0.   ],
1872                [-0.244,  0.969,  0.   ],
1873                [-1.   ,  0.   ,  0.   ],
1874                ..., 
1875                [-0.992,  0.094,  0.   ],
1876                [-0.78 , -0.63 ,  0.   ],
1877                [ 0.969, -0.244,  0.   ]])
1878 
1879 
1880         """ 
1881         pxpy = self.rx[:,recs,1,0]
1882         pzwl = self.rx[:,recs,1,1]
1883         m1m2 = self.rx[:,recs,1,2]
1884         bdfl = self.rx[:,recs,1,3]
1885 
1886         ipx = pxpy & np.uint16(0xFF)
1887         ipy = (pxpy & np.uint16(0xFF00)) >> 8
1888         ipz = pzwl & np.uint16(0xFF) 
1889 
1890         m1 = m1m2 & np.uint16(0xFF)  
1891         m2 = (m1m2 & np.uint16(0xFF00)) >> 8 
1892         bd = bdfl & np.uint16(0xFF)  
1893         fl = (bdfl & np.uint16(0xFF00)) >> 8   
1894 
1895         px = ipx.astype(np.float32)/127. - 1.
1896         py = ipy.astype(np.float32)/127. - 1.
1897         pz = ipz.astype(np.float32)/127. - 1.
1898 
1899         pol = np.empty( (len(px), 3))
1900         pol[:,0] = px
1901         pol[:,1] = py
1902         pol[:,2] = pz
1903 
1904         return pol
1905 
1906     def rpol_bins(self):
1907         """
1908         Avoiding artifacts for char compressed, means 
1909         using the compression binning.
1910 
1911         Suspect one bin difference in handling somewhere ?
1912 
1913         :: 
1914 
1915               py = ipy/127. - 1.
1916               plt.hist(py, bins=np.linspace(-1,1,255) )
1917               plt.show()
1918               plt.hist(py, bins=np.linspace(-1,1,255) )
1919               hist -n
1920 
1921         """
1922         #lo = np.uint16(0x0)/127. - 1. 
1923         #hi = np.uint16(0xFF)/127. - 1.  # 1.0078740157480315 
1924         #lb = np.linspace(lo, hi, 255+1+1)
1925         lb = np.linspace(-1, 1, 255+1)   # 
1926         return lb 
1927 
1928 
1929     def recflags(self, recs):
1930         """
1931 
1932         In [18]: a.rx.shape
1933         Out[18]: (11235, 10, 2, 4)
1934 
1935         Saved from optixrap/cu/photon.h:rsave 
1936         """
1937         m1m2 = self.rx[:,recs,1,2]
1938         bdfl = self.rx[:,recs,1,3]
1939 
1940         m1 = m1m2 & np.uint16(0xFF)  
1941         m2 = (m1m2 & np.uint16(0xFF00)) >> 8 
1942         bd = bdfl & np.uint16(0xFF)  
1943         fl = (bdfl & np.uint16(0xFF00)) >> 8   
1944 
1945         flgs = np.empty( (len(m1), 4), dtype=np.int32)
1946         flgs[:,0] = m1
1947         flgs[:,1] = m2
1948         flgs[:,2] = np.int8(bd)
1949         flgs[:,3] = fl
1950         return flgs
1951 
1952 
1953     m1 = property(lambda self:(self.rx[:,:,1,2] & np.uint16(0xFF) ))
1954     m2 = property(lambda self:(self.rx[:,:,1,2] & np.uint16(0xFF00)) >> 8 )
1955     bd = property(lambda self:np.int8(self.rx[:,:,1,3] & np.uint16(0xFF) ))
1956     fl = property(lambda self:(self.rx[:,:,1,3] & np.uint16(0xFF00)) >> 8 )
1957 
1958 
1959     def rflgs_(self, recs):
1960         return self.recflags(recs) 
1961 
1962     def post_center_extent(self):
1963         """
1964         :return two quads: eg  (array([0., 0., 0., 0.]), array([451.  , 451.  , 451.  ,   9.02]))
1965         """ 
1966         p_center = self.fdom[0,0,:W] 
1967         p_extent = self.fdom[0,0,W] 
1968 
1969         t_center = self.fdom[1,0,X]
1970         t_extent = self.fdom[1,0,Y]
1971 
1972         center = np.zeros(4)
1973         center[:W] = p_center 
1974         center[W] = t_center
1975 
1976         extent = np.zeros(4)
1977         extent[:W] = p_extent*np.ones(3)
1978         extent[W] = t_extent
1979 
1980         return center, extent 
1981 
1982     def tbins(self):
1983         t_center = self.fdom[1,0,X]
1984         t_extent = self.fdom[1,0,Y]
1985         assert(t_center == 0.)
1986         tb = np.linspace(t_center, t_center + t_extent, 32767+1)
1987         return tb 
1988 
1989     def pbins(self):
1990         """
1991         compression bins
1992         """
1993         p_center = self.fdom[0,0,:W]
1994         p_extent = self.fdom[0,0,W]
1995         log.debug(" pbins p_center %s p_extent %s " % (repr(p_center), repr(p_extent)))
1996         #assert p_center[0] == p_center[1] == p_center[2] == 0., p_center
1997         pb = np.linspace(p_center[0] - p_extent, p_center[1] + p_extent, 2*32767+1)
1998         return pb 
1999 
2000     def rpost(self):
2001         """
2002         :return record sliced position time array:  sliced by self.recs eg slice(0,1,None) using self.rpost_   
2003         """
2004         recs = self.recs
2005         if recs is None:
2006             log.warning("this only works on evt with single line seqs")
2007             return None
2008         return self.rpost_(recs)
2009 
2010     def rdir(self, fr=0, to=1, nrm=True):
2011         """
2012         :param fr:
2013         :param to:
2014         :param nrm:
2015 
2016         Vector between points on the propagation identified by "fr" and "to" 
2017         zero based point indices.
2018         """
2019         fr_ = self.rpost_(fr)
2020         to_ = self.rpost_(to)
2021         step = to_[:,:3] - fr_[:,:3] 
2022 
2023         if nrm:
2024             dir_ = norm_(step)
2025         else:
2026             dir_ = step 
2027         pass
2028         return dir_
2029 
2030     def rpost_(self, recs=slice(None)):
2031         """
2032         :param recs: record index 0 to 9 (or 0 to 15 for higher bouncemax) or slice therof  
2033 
2034 
2035         NB recs can be a slice, eg slice(0,5) for 1st 5 step records of each photon
2036 
2037         Record compression can be regarded as a very early choice of binning, 
2038         as cannot use other binnings without suffering from artifacts
2039         so need to access the "compression bins" somehow::
2040 
2041             In [24]: cf.a.rx[:,1,0,3].min()
2042             Out[24]: A(16, dtype=int16)
2043 
2044             In [25]: cf.a.rx[:,1,0,3].max()
2045             Out[25]: A(208, dtype=int16)
2046 
2047         The range within some selection is irrelevant, what matters is the 
2048         domain of the compression, need to find that binning then throw
2049         away unused edge bins according to the plotted range. 
2050         """
2051         pass 
2052         post_center, post_extent = self.post_center_extent()  # center and extent are quads, created by combining position and time domain ce 
2053         p = self.rx[:,recs,0].astype(np.float32)*post_extent/32767.0 + post_center 
2054         return p 
2055 
2056     def rpostr(self, recs=slice(None)):
2057         """
2058         :param recs: record index within the photon eg 0..9 or a slice range 
2059         :return r: radius of each point along the photon path 
2060         """
2061         p = self.rpost_(recs)
2062         pos = p[:,:,:3]
2063         pp = pos*pos
2064         r = np.sqrt(pp.sum(axis=2)) 
2065         return r
2066 
2067     def rpostd01(self, recs=slice(None)):
2068         """
2069         :param recs: record index within the photon eg 0..9 or a slice range 
2070         :return d01: distance 0th to 1st point 
2071 
2072         TODO: do this for all points 
2073         """
2074         p = self.rpost_(recs)
2075         pos = p[:,:,:3]
2076         dpos = pos[:, 1] - pos[:, 0]
2077 
2078         d01 = np.sqrt(np.sum(dpos*dpos, axis=1))   
2079         return d01
2080 
2081 
2082     def rpostt(self, recs=slice(None)):
2083         """
2084         :param recs: record index within the photon eg 0..9 or a slice range 
2085         :return t: time at each point along the photon path 
2086         """
2087         p = self.rpost_(recs)
2088         t = p[:,:,3]
2089         return t 
2090 
2091 
2092     def _get_rposta(self):
2093         """
2094         all record array, using slice(None) with rpost_ 
2095         """
2096         return self.rpost_(slice(None))
2097     rposta = property(_get_rposta) 
2098 
2099     def rposti(self, i):
2100         """
2101         Returns all step points of photon i 
2102         """  
2103         return self.rpost_(slice(0,self.npo[i]))[i] 
2104 
2105     def rpostn(self, n):
2106         """
2107         Returns record points of all photons with n record points  
2108 
2109         Hmm rpostn(1) giving all ? 
2110 
2111         """  
2112         wpo = self.wpo[n]   # indices of photons with n points
2113         return self.rpost_(slice(0,n))[wpo] 
2114 
2115 
2116 
2117     @classmethod
2118     def _incident_angle(cls, p0, p1, center=[0,0,0]):
2119         """
2120         Thinking of parallel beam incident on a sphere at center
2121         """
2122         pass
2123         cen = np.tile(center, len(p1)).reshape(-1,3)
2124         pin = p1 - p0
2125         nrm = p1 - cen 
2126         ct = costheta_(nrm, -pin)
2127         return ct 
2128 
2129     def incident_angle(self, center=[0,0,0]):
2130         """
2131         Thinking of parallel beam incident on a sphere at center
2132         """
2133         pass
2134         p0 = self.rpost_(0)[:,:3]
2135         p1 = self.rpost_(1)[:,:3]
2136         return self._incident_angle(p0,p1, center)
2137 
2138 
2139     @classmethod
2140     def _deviation_angle(cls, p_out, side=None, incident=None):
2141 
2142         if side is None:
2143             side = np.array([1,0,0]) 
2144 
2145         if incident is None:
2146             incident = np.array([0,0,-1]) 
2147 
2148         if len(side.shape) == 1:
2149             side = np.tile(side, len(p_out)).reshape(-1,3)
2150 
2151         if len(incident.shape) == 1:
2152             incident  = np.tile(incident, len(p_out)).reshape(-1,3)
2153 
2154         assert np.sum(side*incident) == 0., "side vector must be perpendicular to incident vectors"
2155 
2156         cside = costheta_(p_out, side)
2157 
2158         cdv = costheta_(incident, p_out)
2159         dv = np.piecewise( cdv, [cside>=0, cside<0], [np.arccos,lambda _:2*np.pi - np.arccos(_)])  
2160 
2161         return dv 
2162 
2163 
2164 
2165     def a_recside(self, axis=Z):
2166         """
2167         Use a coordinate of the initial position to define side of incidence
2168         unit vector.
2169      
2170         Those at zero are aribitrarily put on one side
2171 
2172         This can be used for defining 0-360 degrees deviation angles 
2173         """
2174         a0 = self.p0[:,axis]
2175         aside  = np.zeros((len(a0),3))
2176         aside[:,axis] = np.piecewise( a0, [a0>0, a0<0, a0==0], [-1,1,1] ) 
2177         return aside 
2178 
2179     def a_side(self, axis=X):
2180         """
2181         :return: (N,3) unit vector array pointing along the axis of initial generated position  
2182 
2183         #. generation side of initial photon position
2184         #. NB does not require photon step records, just the c4 photon flags  
2185 
2186         """
2187         posaxis = (self.c4.x & (0x1 << axis)) != 0   
2188         vside = np.zeros( (len(posaxis), 3), dtype=np.float32)
2189         vside[:,axis][posaxis] = -1.
2190         vside[:,axis][~posaxis] = 1.
2191         return vside
2192 
2193     def a_deviation_angle(self, incident=None, axis=Z):
2194         vside = self.a_side(axis=axis)
2195         return self._deviation_angle(self.p_out, side=vside, incident=incident)  
2196 
2197     def deviation_angle(self, side=None, incident=None):
2198         """
2199         Deviation angle for parallel squadrons of incident photons 
2200         without assuming a bounce count
2201         """
2202         p_out = self.ox[:,1, :3]  # final/last direction (bounce limited)
2203         return self._deviation_angle(p_out, side=side, incident=incident)
2204 
2205     p0 = property(lambda self:self.rpost_(0))
2206     p_out = property(lambda self:self.ox[:,1, :3], doc="final/last direction (bounce limited)")
2207 
2208     def deviation_angle(self, side=None, incident=None):
2209         """
2210         Deviation angle for parallel squadrons of incident photons 
2211         without assuming a bounce count
2212         """
2213         return self._deviation_angle(self.p_out, side=side, incident=incident)
2214 
2215 
2216     def rsmry_(self, i):
2217         flgs = self.rflgs_(i)
2218 
2219         m1s = np.unique(flgs[:,0])
2220         m2s = np.unique(flgs[:,1])
2221         bns = np.unique(flgs[:,2])
2222         fls = np.unique(flgs[:,3])
2223 
2224         flab = self.histype.label
2225         mlab = self.mattype.label
2226 
2227         if len(m1s) == 1 and len(m2s) == 1 and len(bns) == 1 and len(fls) == 1:
2228             m1 = m1s[0]
2229             m2 = m2s[0]
2230             bn = bns[0]
2231             abn = abs(bn) - 1
2232             fl = fls[0]
2233             smry="m1/m2 %3d/%3d %2s/%2s %4d (%3d) %3d:%s " % (m1,m2,mlab(m1),mlab(m2),bn,abn,fl,flab(fl))
2234         else:
2235             smry=repr(flgs) 
2236         pass
2237         return smry
2238 
2239 
2240     def zrt_profile(self, n, pol=True):
2241         """
2242         :param n: number of bounce steps 
2243         :return: min, max, mid triplets for z, r and t  at n bounce steps
2244 
2245         ::
2246 
2247             In [7]: a_zrt
2248             Out[7]: 
2249             array([[ 300.    ,  300.    ,  300.    ,    1.1748,   97.0913,   49.133 ,    0.1001,    0.1001,    0.1001],
2250                    [  74.2698,  130.9977,  102.6337,    1.1748,   97.0913,   49.133 ,    0.9357,    1.2165,    1.0761],
2251                    [  56.0045,  127.9946,   91.9996,    1.1748,   98.1444,   49.6596,    0.9503,    1.3053,    1.1278]])
2252 
2253 
2254         """
2255         slab = "z r t"
2256         if pol:
2257             slab += " lx ly lz"
2258 
2259         labs = slab.split()
2260         nqwn = 3
2261         zrt = np.zeros((n,len(labs)*nqwn))
2262         tfmt = "%10.3f " * nqwn
2263         fmt = " ".join(["%s: %s " % (lab, tfmt) for lab in labs])
2264 
2265         for i in range(n):
2266             p = self.rpost_(i)
2267             l = self.rpol_(i)
2268             lx = l[:,0]
2269             ly = l[:,1]
2270             lz = l[:,2]
2271 
2272             #r = np.linalg.norm(p[:,:2],2,1)
2273             r = vnorm(p[:,:2])
2274             z = p[:,2]
2275             t = p[:,3]
2276 
2277             assert len(r)>0
2278             assert len(z)>0
2279 
2280             zrt[i][0:3] = mmm(z)
2281             zrt[i][3:6] = mmm(r)
2282             zrt[i][6:9] = mmm(t)
2283 
2284             if pol:
2285                 zrt[i][9:12] = mmm(lx)
2286                 zrt[i][12:15] = mmm(ly)
2287                 zrt[i][15:18] = mmm(lz)
2288 
2289             smry = self.rsmry_(i)
2290 
2291             szrt =  fmt % tuple(zrt[i].tolist())
2292             print("%3d %s smry %s " % (i, szrt, smry ))
2293 
2294         pass
2295         return zrt 
2296 
2297 
2298 def mmm(a):
2299     """
2300     :param a: numpy array
2301     :return: min,max,mid
2302     """
2303     amin = a.min()
2304     amax = a.max()
2305     amid = (amin+amax)/2.
2306     return amin, amax, amid
2307 
2308 
2309 def check_wavelength(evt):
2310     """
2311     Compare uncompressed photon final wavelength 
2312     with first record compressed wavelength
2313     """
2314 
2315     pwl = evt.wl
2316     rwl = evt.recwavelength(0)
2317     dwl = np.absolute(pwl - rwl)
2318     assert dwl.max() < 2. 
2319 
2320 
2321 def deviation_plt(evt):
2322     dv = evt.a_deviation_angle(axis=X)
2323     if plt:
2324         plt.close()
2325         plt.ion()
2326     
2327         plt.hist(dv/deg, bins=360, log=True, histtype="step") 
2328         plt.show()
2329 
2330 
2331 if __name__ == '__main__':
2332     from opticks.ana.main import opticks_main
2333     ok = opticks_main()
2334 
2335     a = Evt(tag="%s"%ok.utag, src=ok.src, det=ok.det, pfx=ok.pfx, args=ok)
2336     if hasattr(a, 'seqhis_ana'):
2337         expr="a.seqhis_ana.table[0:20]"
2338         print(expr)
2339         print(eval(expr))
2340     pass
2341 
2342 
2343     als = a.make_seqhis_ls()  # opticks.ana.seq.SeqList
2344     qq="als[:10]"
2345     for q in qq.split():
2346         print(q)
2347         print(eval(q))
2348     pass 
2349 
2350 
2351 
2352   
2353