Back to home page

EIC code displayed by LXR

 
 

    


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

0001 #!/usr/bin/env python
0002 """
0003 snap.py : analysis of scan metadata
0004 ======================================
0005 
0006 ::
0007 
0008 
0009     snap.py                    # list the snaps in speed order with labels 
0010 
0011     open $(snap.py --jpg)      # open the jpg ordered by render speed
0012 
0013 
0014 
0015 Typical call stack using snap.py::
0016 
0017     ~/o/CSGOptiX/elv.sh
0018     ~/o/CSGOptiX/cxr.sh : jstab
0019     ~/o/bin/BASE_grab.sh : jstab
0020     snap.py 
0021 
0022 The reason for this stack is to do the definition of the BASE directory 
0023 in one place and not repeat that. 
0024 
0025 Which inputs are present depends on recent scan runs::
0026 
0027     ~/o/CSGOptiX/cxr_scan.sh 
0028 
0029 
0030 """
0031 import os, sys, logging, glob, json, re, argparse 
0032 log = logging.getLogger(__name__)
0033 import numpy as np
0034 from opticks.ana.rsttable import RSTTable
0035 from opticks.CSG.CSGFoundry import CSGFoundry, LV, MM
0036 
0037 
0038 class MM(object):
0039     """
0040     The input file is now a standard resident of the CSGFoundry directory 
0041     Note that the input file was formerly created using::
0042 
0043        ggeo.py --mmtrim > $TMP/mm.txt
0044        # created list of mm names used for labels at $TMP/mm.txt
0045 
0046     """
0047     PTN = re.compile("\d+") 
0048     def __init__(self, path ):
0049         mm = os.path.expandvars(path)
0050         mm = open(mm, "r").read().splitlines() if os.path.exists(mm) else None
0051         self.mm = mm
0052         if mm is None:
0053             log.fatal("missing %s, which is now a standard part of CSGFoundry " % path  ) 
0054             sys.exit(1)
0055         pass
0056 
0057     def imm(self, emm):
0058         return list(map(int, self.PTN.findall(emm))) 
0059 
0060     def label(self, emm):
0061         imm = self.imm(emm)
0062         labs = [self.mm[i] for i in imm] 
0063         lab = " ".join(labs)
0064 
0065         tilde = emm[0] == "t" or emm[0] == "~"
0066         pfx = (  "NOT: " if tilde else "     " ) 
0067 
0068         if emm == "~0" or emm == "t0":
0069             return "ALL"
0070         elif imm == [1,2,3,4]:
0071             return "ONLY PMT"
0072         elif "," in emm:
0073             return ( "EXCL: " if tilde else "ONLY: " ) +  lab 
0074         else:
0075             return lab  
0076         pass
0077 
0078     def __repr__(self):
0079         return "\n".join(self.mm)
0080 
0081 
0082 class DummyCandleSnap(object):
0083     def __init__(self):
0084         self.av = 1 
0085 
0086 
0087 class Snap(object):
0088     """
0089 
0090     cxr_overview_emm_t0,_elv_t_moi__ALL00000.jpg
0091     cxr_overview_emm_t0,_elv_t_moi__ALL.jpg 
0092 
0093     """
0094     PTN0 = re.compile("(?P<pfx>cxr_\S*)_emm_(?P<emm>\S*)_elv_(?P<elv>\S*)_moi_(?P<moi>\S*)(?P<jdx>\d{5})")
0095     PTN1 = re.compile("(?P<pfx>cxr_\S*)_emm_(?P<emm>\S*)_elv_(?P<elv>\S*)_moi_(?P<moi>\S*)")
0096 
0097     PTN_EMM_ELV = re.compile("emm_(?P<emm>\S+?)_elv_(?P<elv>\S+?)_")
0098 
0099     @classmethod
0100     def is_valid(cls, jpg_path):
0101         #json_path = cls.find_json(jpg_path) 
0102         json_path = jpg_path.replace(".jpg", ".json")
0103         valid = (not json_path is None) and json_path[-5:] == ".json"
0104         log.debug("is_valid %r %r %r " % (jpg_path, json_path, valid ) ) 
0105         return valid 
0106 
0107     @classmethod
0108     def ParseStem_OLD(cls, jpg_stem):   
0109         m0 = cls.PTN0.match(jpg_stem)
0110         m1 = cls.PTN1.match(jpg_stem)
0111         m = m0
0112         if m0 is None:
0113             m = m1 
0114         pass 
0115         d = m.groupdict() if not m is None else {}
0116         return d
0117 
0118     @classmethod
0119     def ParseStem(cls, jpg_stem):   
0120         m = cls.PTN_EMM_ELV.search(jpg_stem)
0121         d = m.groupdict() if not m is None else {}
0122         return d
0123 
0124     def __init__(self, jpg_path, selectmode="elv" ):
0125         """
0126         1. determine path to json sidecar by replacing extension .jpg with .json
0127         2. load json 
0128         3. access some json values such as av
0129         4. parse the stem to give elv, emm, moi
0130         """ 
0131         json_path = jpg_path.replace(".jpg", ".json")
0132         jpg_stem = os.path.splitext(os.path.basename(jpg_path))[0]
0133         #log.info("jpg_path %s " % (jpg_path))          
0134         #log.info("json_path %s " % (json_path))          
0135         js = json.load(open(json_path,"r"))
0136 
0137         self.js = js 
0138         self.jpg = jpg_path
0139         self.path = json_path 
0140         self.av = js['av'] 
0141         self.argline = js.get('argline', "no-argline")
0142 
0143         dstem = self.ParseStem(jpg_stem)
0144 
0145         elv = dstem.get("elv", None)
0146         emm = dstem.get("emm", None)
0147         moi = dstem.get("moi", None)
0148         jdx = dstem.get("jdx", None)
0149 
0150         #log.info("jpg_stem: %s" % jpg_stem )
0151         #log.info(" dstem: %s " % str(dstem))
0152 
0153         self.dstem = dstem  
0154         self.elv = elv
0155         self.emm = emm 
0156         self.moi = moi
0157         self.jdx = jdx
0158 
0159         self.selectmode = selectmode
0160         self.enabled = elv if selectmode == "elv" else emm 
0161 
0162         # below are set by SnapScan after sorting
0163         self.sc = None   
0164         self.idx = None  
0165 
0166     def jpg_(self):
0167         """
0168         :return jpg_path: avoiding strings that cause RST issues
0169 
0170         tilde ~ and __t0__ causing RST problems : so replace tham 
0171         """
0172         fold = os.path.dirname(self.jpg)
0173         tname = self.jpg_tname()
0174         return os.path.join(fold, tname)
0175 
0176     def jpg_tname(self): 
0177         """
0178         This is a workaround for problems with s5 machinery for names 
0179         containing strings like the below, presumably due to some RST meaning 
0180         the names get mangled preventing the association between presentation pages and 
0181         background image definition causes the images to not appear::
0182 
0183             __~0__
0184             __t0__
0185             _ALL_
0186 
0187         ACTUALLY THIS ISSUE MAY BE FROM NON-UNIQUE IDENTIFIERS DERIVED FROM THE 
0188         SLIDE TITLES BY REMOVING SOME CHARS SUCH AS "," "_" "~"
0189         """
0190         name = os.path.basename(self.jpg)
0191         tname = name.replace("~","t")
0192         tname = tname.replace("__t0__", "_all_") 
0193         return tname 
0194 
0195     def mvjpg(self):
0196         """
0197         :return cmdstring: to change the name of the jpg to jpg_tname avoiding RST issues   
0198         """
0199         name = os.path.basename(self.jpg)
0200         tname = self.jpg_tname()
0201         return None if name == tname else "mv %s %s" % (name, tname)
0202 
0203     def cpjpg(self, pfx, s5base):
0204         """
0205         :return cmdstring: that copies the absolute jpg path into presentation tree
0206         """
0207         s5base = os.path.expandvars(s5base)
0208         name = os.path.basename(self.jpg)
0209         ppath = "%s%s/%s"%(s5base,pfx,name)
0210         if os.path.exists(ppath):
0211             ret = None   
0212         else:
0213             ret = "cp %s %s" % (self.jpg, ppath)
0214         return ret 
0215 
0216     def refjpg(self, pfx, afx="1280px_720px", indent="    "): 
0217         """
0218         :return cmdstring: for inclusion into s5_background_image.txt 
0219         """
0220         name = os.path.basename(self.jpg_())
0221         return "\n".join([indent+self.title(), indent+pfx+"/"+name+" "+afx, ""])
0222 
0223     def title(self):
0224         name = os.path.basename(self.jpg_())
0225         stem = name.replace(".jpg","") 
0226         return "[%d]%s" % (self.idx, stem)
0227  
0228     def pagetitle(self,kls="blue"):
0229         return ":%s:`%s`" % (kls,self.title())
0230  
0231     def pagejpg(self):
0232         title = self.pagetitle()
0233         return "\n".join([title, "-" * len(title), ""])
0234 
0235 
0236     over_fast = property(lambda self:float(self.av)/float(self.sc.fast.av))
0237     over_slow = property(lambda self:float(self.av)/float(self.sc.slow.av))
0238     over_candle = property(lambda self:float(self.av)/float(self.sc.candle.av))
0239 
0240     label = property(lambda self:self.sc.label(self.enabled))
0241     imm = property(lambda self:self.sc.mm.imm(self.emm))
0242 
0243     def row(self):
0244         return  (int(self.idx), self.enabled, self.av, self.over_candle, self.label )
0245 
0246 
0247     SPACER = "    "
0248     LABELS = ["idx", "-e", "time(s)", "relative", "enabled geometry description" ] 
0249     WIDS = [     3,     10,     10,        10,       70 ] 
0250     HFMT = [  "%3s",  "%10s", "%10s",   "%10s",   "%-70s" ]  
0251     RFMT = [  "%3d", "%10s", "%10.4f", "%10.4f",  "%-70s" ]  
0252     PRE  =  [  ""   , ""    , SPACER , SPACER  ,  SPACER ] 
0253     POST =  [  ""   , ""    , SPACER , SPACER  ,  SPACER ]  
0254 
0255 
0256     def __repr__(self):
0257         RFMT = [ self.PRE[i] + self.RFMT[i] +self.POST[i] for i in range(len(self.RFMT))]
0258         rfmt = " ".join(RFMT)
0259         #print(rfmt)
0260         return rfmt % self.row()
0261 
0262     @classmethod
0263     def Hdr(cls):
0264         HFMT = [cls.PRE[i] + cls.HFMT[i] + cls.POST[i] for i in range(len(cls.HFMT))]
0265         hfmt = " ".join(HFMT)
0266         labels = cls.LABELS
0267         return hfmt % tuple(labels)
0268 
0269 
0270 class SnapScan(object):
0271     @classmethod
0272     def MakeSnaps(cls, globptn_, reverse=False, selectmode="elv"):
0273         """
0274         1. resolve globptn into a sorted list of paths, typically of jpg render files
0275         2. select paths considered valid, currently all paths found
0276         3. create Snap instances for every path 
0277         4. order the Snap instances based in Snap.av obtained from the sidecar json file
0278         5. return the snaps
0279         """ 
0280         globptn = os.path.expandvars(globptn_)
0281         log.info("globptn_ %s globptn %s " % (globptn_, globptn) )
0282         raw_paths = glob.glob(globptn) 
0283         log.info("globptn raw_paths %d : 1st %s " % (len(raw_paths), raw_paths[0]))
0284         paths = list(filter(lambda p:Snap.is_valid(p), raw_paths))  # seems all paths are for now valid
0285         log.info("after is_valid filter len(paths): %d " % len(paths))
0286         snaps = list(map(lambda p:Snap(p,selectmode=selectmode),paths))
0287         snaps = list(sorted(snaps, key=lambda s:s.av, reverse=reverse))
0288         return snaps
0289 
0290     @classmethod
0291     def SelectSnaps_imm(cls, all_snaps, selectspec):
0292         """
0293         Selection of snaps based on the imm list of ints for each snap
0294         """
0295         snaps = []
0296         for s in all_snaps:  
0297             #print(s.imm)
0298             if len(s.imm) in [1,] or s.imm == [8, 0] or s.imm == [1,2,3,4]:
0299                 snaps.append(s)
0300             else:
0301                 log.info("skip %s " % str(s.imm))
0302             pass   
0303         pass
0304         return snaps
0305 
0306     @classmethod
0307     def SelectSnaps_elv(cls, all_snaps, selectspec):
0308         SNAP_LIMIT = int(os.environ.get("SNAP_LIMIT", "256" ))
0309         snaps = []
0310         for s in all_snaps:  
0311             #print("s.elv %s " % s.elv)
0312             if selectspec == "all":
0313                 snaps.append(s)
0314             elif selectspec == "not_elv_t":
0315                 if not s.elv.startswith("t"):
0316                     snaps.append(s)
0317                 pass
0318             elif selectspec == "only_elv_t":
0319                 if s.elv.startswith("t"):
0320                     snaps.append(s)
0321                 pass
0322             else: 
0323                 log.fatal("selectspec %s must be one of : all not_elv_t only_elv_t " % selectspec ) 
0324                 assert 0
0325                 pass
0326             pass
0327         pass
0328         lim_snaps = snaps[:SNAP_LIMIT]
0329         log.info("all_snaps %d selectspec %s snaps %d SNAP_LIMIT %d lim_snaps %d " % (len(all_snaps), selectspec, len(snaps), SNAP_LIMIT, len(lim_snaps) ))
0330         return lim_snaps
0331 
0332 
0333     @classmethod
0334     def GEOMInfo(cls):
0335         """
0336         Returns MM and LV instances
0337 
0338         """ 
0339         cfptn = "$HOME/.opticks/GEOM/$GEOM/CSGFoundry"
0340         cfdir = os.path.expandvars(cfptn)  
0341         log.info("cfptn %s cfdir %s " % (cfptn, cfdir) ) 
0342 
0343         mmlabel_path = os.path.join(cfdir, "mmlabel.txt")
0344         log.info("mmlabel_path %s " % mmlabel_path ) 
0345         mm = MM(mmlabel_path)
0346 
0347         meshname_path = os.path.join(cfdir, "meshname.txt")
0348         log.info("meshname_path %s " % meshname_path ) 
0349         lv = LV(meshname_path)
0350 
0351         return mm, lv
0352 
0353 
0354     @classmethod
0355     def Create(cls, globptn, reverse, selectmode, selectspec, candle):
0356         """
0357         :param globptn:
0358         :param reverse: bool
0359         :param selectmode: "imm" or "elv" or "all"
0360         :param selectspec: argument depending on selectmode
0361         :param candle:
0362         """
0363         ss = cls(globptn, candle=candle, reverse=reverse, selectmode=selectmode, selectspec=selectspec  )
0364         return ss 
0365 
0366     # SnapScan
0367     def __init__(self, globptn, candle="1,2,3,4", reverse=False, selectmode="elv", selectspec=""):
0368         """
0369         :param globptn: eg 
0370 
0371         1. find all_snaps Snap instances using the globptn 
0372         2. set s_candle to the Snap instance for which s.enabled matches candle, eg "1,2,3,4"
0373 
0374 
0375         """
0376         mm,lv = self.GEOMInfo()
0377         self.mm = mm
0378         self.lv = lv
0379         self.selectmode = selectmode
0380         self.selectspec = selectspec
0381 
0382         all_snaps = self.MakeSnaps(globptn, reverse=reverse, selectmode=selectmode)
0383         s_candle = None
0384 
0385         n_candle = 0 
0386 
0387         for s in all_snaps:
0388             s.sc = self
0389             if s.enabled == candle:
0390                 s_candle = s
0391                 n_candle += 1 
0392             pass  
0393         pass
0394         log.info("all_snaps:%d candle:%s n_candle:%d selectmode:%s " % (len(all_snaps), candle, n_candle, selectmode ) )
0395 
0396         # filter out the double emm
0397 
0398         if self.selectmode in ["imm","emm"]:
0399             snaps = self.SelectSnaps_imm(all_snaps, selectspec)  # based on imm list of ints 
0400         elif self.selectmode == "elv":
0401             snaps = self.SelectSnaps_elv(all_snaps, selectspec)
0402         elif self.selectmode == "all":
0403             snaps = all_snaps
0404         else:
0405             snaps = all_snaps
0406         pass
0407 
0408         log.info("after selectmode:%s selectspec:%s snaps:%d " % (selectmode, selectspec, len(snaps)))
0409 
0410         for idx, s in enumerate(snaps):
0411             s.idx = idx
0412         pass
0413         self.snaps = snaps
0414         self.all_snaps = all_snaps
0415 
0416         if s_candle is None:
0417             s_candle = DummyCandleSnap()
0418         pass  
0419         self.candle = s_candle 
0420 
0421     def label(self, enabled):
0422         #log.info(" enabled : %s " % str(enabled))  
0423         return self.lv.label(enabled) if self.selectmode == "elv" else self.mm.label(enabled)
0424 
0425     def table(self):
0426         table = np.empty([len(self.snaps),len(Snap.LABELS)], dtype=np.object ) 
0427         for idx, snap in enumerate(self.snaps):
0428             table[idx] = snap.row()
0429         pass
0430         return table
0431 
0432     def rst_table(self):
0433         t = self.table()
0434 
0435         labels = Snap.LABELS 
0436         #labels[-1] += " " + self.cfdigest[:8]
0437 
0438         rst = RSTTable.Render(t, labels, Snap.WIDS, Snap.HFMT, Snap.RFMT, Snap.PRE, Snap.POST )
0439         return rst 
0440 
0441 
0442     fast = property(lambda self:self.snaps[0])
0443     slow = property(lambda self:self.snaps[-1])
0444 
0445     def __repr__(self):
0446         return "\n".join( [Snap.Hdr()] + list(map(repr,self.snaps)) + [Snap.Hdr()] ) 
0447 
0448     def jpg(self):
0449         return "\n".join(list(map(lambda s:s.jpg,self.snaps)))
0450 
0451     def mvjpg(self):
0452         return "\n".join(list(filter(None,map(lambda s:s.mvjpg(),self.snaps))))
0453 
0454     def cpjpg(self, pfx, s5base):
0455         return "\n".join(list(filter(None,map(lambda s:s.cpjpg(pfx, s5base),self.snaps))))
0456 
0457     def argline(self):
0458         return "\n".join(list(map(lambda s:s.argline,self.snaps)))
0459 
0460     def refjpg(self, pfx): 
0461         return "\n".join(list(map(lambda s:s.refjpg(pfx),self.snaps)))
0462 
0463     def pagejpg(self): 
0464         return "\n".join(list(map(lambda s:s.pagejpg(),self.snaps)))
0465 
0466 
0467 def edef(key, fallback, prefix="SNAP_"):
0468     return os.environ.get(prefix+key,fallback)
0469         
0470 def parse_args(doc, **kwa):
0471     np.set_printoptions(suppress=True, precision=3, linewidth=200)
0472     parser = argparse.ArgumentParser(doc)
0473 
0474     SNAP_level      = edef("level", "info")
0475     SNAP_globptn    = edef("globptn", "$TMP/snap/*.jpg")
0476     SNAP_refjpgpfx  = edef("refjpgpfx", "/env/presentation/snap/lLowerChimney_phys" ) 
0477     SNAP_s5base     = edef("s5base", "$HOME/simoncblyth.bitbucket.io")
0478     SNAP_outpath    = edef("outpath", "/tmp/ana_snap.txt" )
0479     SNAP_selectmode = edef("selectmode", "elv" )
0480     SNAP_selectspec = edef("selectspec", "not_elv_t")
0481     SNAP_candle     = edef("candle","t")   
0482     SNAP_dump       = edef("dump","txt")
0483     SNAP_out        = edef("out",True) 
0484     SNAP_reverse    = edef("reverse",False) 
0485 
0486     # config 
0487     parser.add_argument(  "--level",       default=SNAP_level,      help="logging level" ) 
0488     parser.add_argument(  "--globptn",     default=SNAP_globptn,    help="base" ) 
0489     parser.add_argument(  "--refjpgpfx",   default=SNAP_refjpgpfx,  help="List jpg paths s5 background image presentation format" ) 
0490     parser.add_argument(  "--s5base",      default=SNAP_s5base,     help="Presentation repo base" )
0491     parser.add_argument(  "--outpath",     default=SNAP_outpath,    help="Path where to write output" )
0492     parser.add_argument(  "--selectmode",  default=SNAP_selectmode, help="SnapScan selection mode" )
0493     parser.add_argument(  "--selectspec",  default=SNAP_selectspec, help="all/only_elv_t/not_elv_t : May be used to configure snap selection" )
0494     parser.add_argument(  "--candle",      default=SNAP_candle,     help="Enabled setting to use as the reference candle for relative column in the table" )
0495     parser.add_argument(  "--out" ,        default=SNAP_out,        help="Enable saving output to *outpath*", action="store_true" )
0496     parser.add_argument(  "--reverse",     default=SNAP_reverse,    help="Reverse time order with slowest first", action="store_true"  )
0497 
0498     # what to output
0499     doc = {}
0500     doc["jpg"] = "Dump list of jpg paths in speed order"
0501     doc["rst"] = "Dump table in RST format"
0502     doc["txt"] = "Dump table in TXT format"
0503     doc["refjpg"] = "List jpg paths s5 background image presentation format"
0504     doc["pagejpg"] = "List jpg for inclusion into s5 presentation" 
0505     doc["mvjpg"] = "List commands to mv the jpg"
0506     doc["cpjpg"] = "List cp commands to place into presentation repo"
0507     doc["argline"] = "List argline in speed order"
0508     doc["snaps"]  = "Debug: just create SnapScan "
0509 
0510     parser.add_argument(  "--dump",    choices=list(doc.keys()), default=SNAP_dump, help="Specify what to dump" ) 
0511 
0512 
0513  
0514     args = parser.parse_args()
0515     fmt = '[%(asctime)s] p%(process)s {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s'
0516     logging.basicConfig(level=getattr(logging,args.level.upper()), format=fmt)
0517     return args  
0518 
0519 
0520 if __name__ == '__main__':
0521     args = parse_args(__doc__)
0522     log.debug(" args %s " % str(args))
0523 
0524     log.info(" args.globptn : %s " % (args.globptn) ) 
0525     log.info(" args.dump    : %s " % (args.dump) )
0526     
0527     ss = SnapScan.Create(args.globptn, args.reverse, args.selectmode, args.selectspec, args.candle) 
0528 
0529     out = None
0530     if args.dump == "jpg":    # list jpg paths in time order 
0531         out = str(ss.jpg())
0532     elif args.dump == "refjpg":
0533         out = str(ss.refjpg(args.refjpgpfx))
0534     elif args.dump == "pagejpg":
0535         out = str(ss.pagejpg())
0536     elif args.dump == "mvjpg":
0537         out = str(ss.mvjpg())
0538     elif args.dump == "cpjpg":
0539         out = str(ss.cpjpg(args.refjpgpfx, args.s5base))
0540     elif args.dump == "argline":
0541         out = str(ss.argline())
0542     elif args.dump == "snaps":
0543         out = str(ss.snaps)
0544     elif args.dump == "rst":
0545         out = str(ss.rst_table())
0546     elif args.dump == "txt":
0547         out = str(ss)
0548     else:
0549         out = str(ss) 
0550     pass
0551     out += "\n"
0552 
0553     if args.out:
0554         log.info("--out writing to %s " % args.outpath)
0555         open(args.outpath, "w").write(out)
0556     else: 
0557         print(out)
0558     pass
0559 
0560