File indexing completed on 2026-04-09 07:48:47
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
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))
0042 m1_ = lambda p:np.sqrt(np.sum(p*p, axis=1))
0043 m2_ = lambda p:np.sqrt(np.sum(p*p, axis=2))
0044
0045
0046 nb_ = lambda a,s:( a & ( 0xf << 4*s )) >> (4*s)
0047
0048
0049
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
0066
0067
0068
0069
0070
0071
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
0083 from opticks.ana.hismask import HisMask
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
0213
0214
0215
0216
0217
0218
0219
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"
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
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
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
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
0349
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 "-")
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 }
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,:]
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" )
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
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])
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
0778
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
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
0880 self.seqhis_ls = self.make_seqhis_ls()
0881 self.pflags2 = seq2msk(allseqhis)
0882
0883
0884
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
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
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 ")
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]
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
1312
1313
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
1357
1358
1359
1360
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
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" )
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
1440 def _get_irec(self):
1441 return self._irec
1442 def _set_irec(self, irec):
1443
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
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
1476
1477
1478
1479 pass
1480 return sel
1481
1482
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
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
1639 fmt = "%Y%m%d-%H"
1640 t = stamp_(p, fmt)
1641 if t is not None:
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)
1720 b_ana = getattr(b, ana_, None)
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)
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
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)
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
1923
1924
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
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()
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]
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]
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
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()
2344 qq="als[:10]"
2345 for q in qq.split():
2346 print(q)
2347 print(eval(q))
2348 pass
2349
2350
2351
2352
2353