diff --git a/CSG/CSGFoundry.py b/CSG/CSGFoundry.py deleted file mode 100644 index e1ea5c94c9..0000000000 --- a/CSG/CSGFoundry.py +++ /dev/null @@ -1,1170 +0,0 @@ -#!/usr/bin/env python -import os, sys, re, numpy as np, logging, datetime -from configparser import ConfigParser -from collections import OrderedDict, Counter -log = logging.getLogger(__name__) - -#from opticks.ana.key import keydir -from opticks.ana.fold import Fold, STR -from opticks.sysrap.OpticksCSG import CSG_ - - - -class KeyNameConfig(object): - """ - Parses ini file of the below form:: - - [key_boundary_regexp] - red = (?PWater/.*/Water$) - blue = Water///Acrylic$ - magenta = Water/Implicit_RINDEX_NoRINDEX_pOuterWaterInCD_pInnerReflector//Tyvek$ - yellow = DeadWater/Implicit_RINDEX_NoRINDEX_pDeadWater_pTyvekFilm//Tyvek$ - pink = Air/CDTyvekSurface//Tyvek$ - cyan = Tyvek//Implicit_RINDEX_NoRINDEX_pOuterWaterInCD_pCentralDetector/Water$ - orange = Water/Steel_surface//Steel$ - grey = - # empty value is special cased to mean all other boundary names - - Defaults path is $HOME/.opticks/GEOM/cxt_min.ini - """ - - - def __init__(self, _path ): - path = os.path.expandvars(_path) - cfg = ConfigParser() - cfg.read(path) - - self._path = _path - self.path = path - self.cfg = cfg - - - def __call__(self, _section): - sect = self.cfg[_section] - bdict = OrderedDict(sect) - - counts = Counter(bdict.values()) - duplicates = [val for val, count in counts.items() if count > 1] - - if duplicates: - log.fatal(f"CSGFoundry.py/KeyNameConfig : Found duplicated values: {duplicates}") - sys.exit(1) - pass - - return bdict - - - - - -class NameTable(object): - def __init__(self, symbol, ix, ixn, d_qix={}, KEY=None): - """ - :param symbol: eg btab or ptab - :param ix: large array of indices (eg boundaries or globalPrimIdx) - :param ixn: smallish array of names - :param d_qix: dict keyed on colors with arrays of ixn indices as values - """ - u, x, c = np.unique(ix, return_index=True, return_counts=True) - - if "KLUDGE" in os.environ: - kludge = u != 0xffff - u = u[kludge] - x = x[kludge] - c = c[kludge] - pass - - # form array with the keys that each boundary index is grouped into - akk = np.repeat("????????????????", len(u)) - for i in range(len(u)): - kk = [] - for k, qix in d_qix.items(): - if len(np.where(np.isin(qix, u[i]))[0]) == 1: - kk.append(k) - pass - pass - akk[i] = ",".join(kk) - pass - n = ixn[u] # IF THIS FAILS FROM 0xffff TRY KLUDGE=1 - o = np.argsort(c)[::-1] - - _tab = "np.c_[akk, u, c, x, n][o]" - tab = eval(_tab) - - self.symbol = symbol - self.akk = akk[o] - self.u = u[o] - self.x = x[o] - self.c = c[o] - self.n = n[o] - - self.KEY = KEY - - self.d_qix = d_qix - self._tab = _tab - self.tab = tab - self.ixn = ixn - - def get_names(self, k): - """ - print("\n".join(cf.primname[cf.cxtp.d_qix['thistle']])) - """ - return self.ixn[self.d_qix[k]] - - def dump_names(self, k): - names = self.get_names(k) - print("\n".join(names)) - - def __repr__(self): - """ - """ - vfmt = " %20s : %4d %8d %8d : %s " - sfmt = " %20s : %4s %8s %8s : %s " - tfmt = " %20s : %4s %8d %8s : %s " - - label = sfmt % ( ".akk", ".u", ".c", ".x", ".n" ) - lines = [] - lines.append("[cf.%s KEY:%s" % (self.symbol, self.KEY) ) - lines.append(label) - - if not self.KEY is None: - if self.KEY.startswith("~"): - KEY = self.KEY[1:] - invert = True - else: - KEY = self.KEY - invert = False - pass - KK = np.array(self.KEY.split(",")) - sel = np.where(np.isin(self.tab[:,0], KK, invert=invert))[0] - else: - sel = slice(None) - pass - - ctot = 0 - for row in self.tab[sel]: - akk = row[0] - u = int(row[1]) - c = int(row[2]) - x = int(row[3]) - n = row[4] - - line = vfmt % ( akk, u, c, x, n ) - ctot += c - lines.append(line) - pass - lines.append(label) - lines.append(tfmt % ("", "",ctot, "",".ctot")) - lines.append("]cf.%s" % self.symbol ) - self.ctot = ctot - return "\n".join(lines) - - - -class GroupedNameTable(object): - def __init__(self, symbol, ix, d_qix, d_anno, cf_ixn, lwid=100): - """ - :param symbol: identifier - :param ix: large array of indices - :param d_qix: dict keyed on colors with indices array values - :param d_anno: dict keyed on colors with label values - :param cf_ixn: small array of all names - :param lwid: width of the label field - """ - self.symbol = symbol - self.ix = ix - self.d_qix = d_qix - self.d_anno = d_anno - self.cf_ixn = cf_ixn - self.lwid = lwid - - wdict = {} - for k,qix in self.d_qix.items(): - _w = np.isin( ix, qix ) # bool array indicating which elem of ix are in the qix array - w = np.where(_w)[0] # indices of ix array with ix names matching the regexp - wdict[k] = w - pass - self.wdict = wdict - - def __repr__(self): - """ - """ - lines = [] - lines.append("[cf.%s___________________________________" % self.symbol ) - fmt = " %%15s %%%(lwid)ds nnn:%%4d %%s " % dict(lwid=self.lwid) - for k,qix in self.d_qix.items(): - _w = np.isin( self.ix, qix ) # bool array indicating which elem of bn are in the qbn array - w = np.where(_w)[0] # indices of bn array with boundaries matching the regexp - label = self.d_anno[k] - nn = self.cf_ixn[qix] - line = fmt % (k, label, len(nn), str(qix[:10])) - lines.append(line) - pass - lines.append("]cf.%s_______ cf.%s.d_qix ____________________________" % ( self.symbol, self.symbol) ) - return "\n".join(lines) - - - - - - -class CSGObject(object): - @classmethod - def Label(cls, spc=5, pfx=10): - prefix = " " * pfx - spacer = " " * spc - return prefix + spacer.join(cls.FIELD) - - @classmethod - def Fields(cls, bi=False): - kls = cls.__name__ - for i, field in enumerate(cls.FIELD): - setattr(cls, field, i) - if bi:setattr(builtins, field, i) - pass - - @classmethod - def Type(cls): - cls.Fields() - kls = cls.__name__ - print("%s.Type()" % kls ) - for i, field in enumerate(cls.FIELD): - name = cls.DTYPE[i][0] - fieldname = "%s.%s" % (kls, field) - print(" %2d : %20s : %s " % (i, fieldname, name)) - pass - print("%s.Label() : " % cls.Label() ) - - @classmethod - def RecordsFromArrays(cls, a): - """ - :param a: ndarray - :return: np.recarray - """ - ra = np.core.records.fromarrays(a.T, dtype=cls.DTYPE ) - return ra - - - -class CSGPrim(CSGObject): - DTYPE = [ - ('numNode', ' $TMP/mm.txt - - """ - PTN = re.compile("\\d+") - def __init__(self, path ): - mm = os.path.expandvars(path) - mm = open(mm, "r").read().splitlines() if os.path.exists(mm) else None - self.mm = mm - if mm is None: - log.fatal("missing %s, which is now a standard part of CSGFoundry " % path ) - sys.exit(1) - pass - - def imm(self, emm): - return list(map(int, self.PTN.findall(emm))) - - def label(self, emm): - imm = self.imm(emm) - labs = [self.mm[i] for i in imm] - lab = " ".join(labs) - - tilde = emm[0] == "t" or emm[0] == "~" - pfx = ( "NOT: " if tilde else " " ) - - if emm == "~0" or emm == "t0": - return "ALL" - elif imm == [1,2,3,4]: - return "ONLY PMT" - elif "," in emm: - return ( "EXCL: " if tilde else "ONLY: " ) + lab - else: - return lab - pass - - def __repr__(self): - return STR("\n".join(self.mm)) - - -class LV(object): - PTN = re.compile("\\d+") - def __init__(self, path): - lv = os.path.expandvars(path) - lv = open(lv, "r").read().splitlines() if os.path.exists(lv) else None - self.lv = lv - if lv is None: - log.fatal("missing %s, which is now a standard part of CSGFoundry " % path ) - sys.exit(1) - pass - - def ilv(self, elv): - return list(map(int, self.PTN.findall(elv))) - - def label(self, elv): - ilv = self.ilv(elv) - mns = [self.lv[i] for i in ilv] - mn = " ".join(mns) - tilde = elv[0] == "t" - lab = "" - if elv == "t": - lab = "ALL" - else: - lab = ( "EXCL: " if tilde else "ONLY: " ) + mn - pass - return lab - - def __str__(self): - return "\n".join(self.lv) - - def __repr__(self): - return "\n".join(["%3d:%s " % (i, self.lv[i]) for i in range(len(self.lv))]) - - - -class Deprecated_NPFold(object): - """ - HMM opticks.ana.fold.Fold looks more developed than this - TODO: eliminate all use of this - """ - INDEX = "NPFold_index.txt" - - @classmethod - def IndexPath(cls, fold): - return os.path.join(fold, cls.INDEX) - - @classmethod - def HasIndex(cls, fold): - return os.path.exists(cls.IndexPath(fold)) - - @classmethod - def Load(cls, *args, **kwa): - return cls(*args, **kwa) - - def __init__(self, *args, **kwa): - fold = os.path.join(*args) - self.fold = fold - self.has_index = self.HasIndex(fold) - - if self.has_index: - self.load_idx(fold) - else: - self.load_fts(fold) - pass - - def load_fts(self, fold): - assert 0 - - def load_idx(self, fold): - keys = open(self.IndexPath(fold),"r").read().splitlines() - aa = [] - kk = [] - ff = [] - subfold = [] - - for k in keys: - path = os.path.join(fold, k) - if k.endswith(".npy"): - assert os.path.exists(path) - log.info("loading path %s k %s " % (path, k)) - a = np.load(path) - aa.append(a) - kk.append(k) - else: - log.info("skip non .npy path %s k %s " % (path, k)) - pass - pass - self.kk = kk - self.aa = aa - self.ff = ff - self.subfold = subfold - self.keys = keys - - - def find(self, k): - return self.kk.index(k) if k in self.kk else -1 - - def has_key(self, k): - return self.find(k) > -1 - - def __repr__(self): - lines = [] - lines.append("NPFold %s " % self.fold ) - for i in range(len(self.kk)): - k = self.kk[i] - a = self.aa[i] - stem, ext = os.path.splitext(os.path.basename(k)) - path = os.path.join(self.fold, k) - lines.append("%10s : %20s : %s " % (stem, str(a.shape), k )) - pass - return "\n".join(lines) - - -class Deprecated_SSim(object): - """ - HMM: this adds little on top of ana.fold.Fold - TODO: get rid of it - """ - BND = "bnd.npy" - - - @classmethod - def Load(cls, simbase): - ssdir = os.path.join(simbase, "SSim") - log.info("SSim.Load simbase %s ssdir %s " % (simbase,ssdir)) - sim = Fold.Load(ssdir) if os.path.isdir(ssdir) else None - return sim - - def __init__(self, fold): - if self.has_key(self.BND): - bnpath = os.path.join(fold, "bnd_names.txt") - assert os.path.exists(bnpath) - bnd_names = open(bnpath,"r").read().splitlines() - self.bnd_names = bnd_names - pass - if hasattr(self, 'bnd_names'): # names list from NP bnd.names metadata - bndnamedict = SSim.namelist_to_namedict(self.bnd_names) - else: - bndnamedict = {} - pass - self.bndnamedict = bndnamedict - pass - - - -class CSGFoundry(object): - FOLD = os.path.expandvars("$TMP/CSG_GGeo/CSGFoundry") - FMT = " %10s : %20s : %s " - - - @classmethod - def namelist_to_namedict(cls, namelist): - nd = {} - if not namelist is None: - nd = dict(zip(range(len(namelist)),list(map(str,namelist)))) - pass - return nd - - - @classmethod - def CFBase(cls): - """ - Precedence order for which envvars are used to derive the CFBase folder is: - - 1. CFBASE_LOCAL - 2. CFBASE_ALT - 3. CFBASE - 4. GEOM - - This allows scripts such as CSGOptiX/cxt_min.sh to set envvars to control - the geometry arrays and meshname.txt etc that is loaded. - For example this could be used such that local analysis on laptop can use - a geometry rsynced from remote. - - """ - if "CFBASE_LOCAL" in os.environ: - cfbase= os.environ["CFBASE_LOCAL"] - note = "via CFBASE_LOCAL" - elif "CFBASE_ALT" in os.environ: - cfbase= os.environ["CFBASE_ALT"] - note = "via CFBASE_ALT" - elif "CFBASE" in os.environ: - cfbase= os.environ["CFBASE"] - note = "via CFBASE" - elif "GEOM" in os.environ: - KEY = os.path.expandvars("${GEOM}_CFBaseFromGEOM") - if KEY in os.environ: - cfbase = os.environ[KEY] - note = "via GEOM,%s" % KEY - else: - cfbase = os.path.expandvars("$HOME/.opticks/GEOM/$GEOM") - note = "via GEOM" - pass - else: - cfbase = None - note = "via NO envvars" - pass - if cfbase is None: - print("CSGFoundry.CFBase returning None, note:%s " % note ) - else: - print("CSGFoundry.CFBase returning [%s], note:[%s] " % (cfbase,note) ) - pass - return cfbase - - @classmethod - def Load(cls, cfbase_=None, symbol=None): - """ - :param cfbase_: typically None, but available when debugging eg comparing two geometries - """ - if cfbase_ is None: - cfbase = cls.CFBase() - else: - cfbase = os.path.expandvars(cfbase_) - pass - log.info("cfbase:%s " % cfbase) - if cfbase is None or not os.path.exists(os.path.join(cfbase, "CSGFoundry")): - print("ERROR CSGFoundry.CFBase returned None OR non-existing CSGFoundry dir so cannot CSGFoundry.Load" ) - return None - pass - assert not cfbase is None - cf = cls(fold=os.path.join(cfbase, "CSGFoundry")) - cf.symbol = symbol - return cf - - @classmethod - def FindDirUpTree(cls, origpath, name="CSGFoundry"): - """ - :param origpath: to traverse upwards looking for sibling dirs with *name* - :param name: sibling directory name to look for - :return full path to *name* directory - """ - elem = origpath.split("/") - found = None - for i in range(len(elem),0,-1): - path = "/".join(elem[:i]) - cand = os.path.join(path, name) - log.debug(cand) - if os.path.isdir(cand): - found = cand - break - pass - pass - return found - - @classmethod - def FindDigest(cls, path): - if path.find("*") > -1: # eg with a glob pattern filename element - base = os.path.dirname(path) - else: - base = path - pass - base = os.path.expandvars(base) - return cls.FindDigest_(base) - - @classmethod - def FindDigest_(cls, path): - """ - :param path: Directory path in which to look for a 32 character digest path element - :return 32 character digest or None: - """ - hexchar = "0123456789abcdef" - digest = None - for elem in path.split("/"): - if len(elem) == 32 and set(elem).issubset(hexchar): - digest = elem - pass - pass - return digest - - def __init__(self, fold): - self.load(fold) - self.meshnamedict = self.namelist_to_namedict(self.meshname) - self.primIdx_meshname_dict = self.make_primIdx_meshname_dict() - - self.mokname = "zero one two three four five six seven eight nine".split() - self.moknamedict = self.namelist_to_namedict(self.mokname) - self.insnamedict = {} - - self.lv = LV(os.path.join(fold, "meshname.txt")) - self.mm = MM(os.path.join(fold, "mmlabel.txt")) - - sim = Fold.Load(fold, "SSim") - - try: - bdn = sim.stree.standard.bnd_names - except AttributeError: - bdn = None - pass - if bdn is None: log.fatal("CSGFoundry fail to access sim.stree.standard.bnd_names : geometry incomplete" ) - if type(bdn) is np.ndarray: sim.bndnamedict = self.namelist_to_namedict(bdn) - pass - - self.kncfg = KeyNameConfig("$HOME/.opticks/GEOM/cxt_min.ini") - - self.bdn_config = self.kncfg("key_boundary_regexp") - self.bdn_config_note = self.kncfg("key_boundary_regexp_note") - - self.prn_config = self.kncfg("key_prim_regexp") - self.prn_config_note = self.kncfg("key_prim_regexp_note") - - self.bdn = bdn - self.sim = sim - - self.npa = self.node.reshape(-1,16)[:,0:6] - self.nbd = self.node.view(np.int32)[:,1,2] - self.nix = self.node.view(np.int32)[:,1,3] - self.nbb = self.node.reshape(-1,16)[:,8:14] - self.ntc = self.node.view(np.int32)[:,3,2] - self.ncm = self.node.view(np.uint32)[:,3,3] >> 31 # node complement - self.ntr = self.node.view(np.uint32)[:,3,3] & 0x7fffffff # node tranIdx+1 - - self.pnn = self.prim.view(np.int32)[:,0,0] # prim num node - self.pno = self.prim.view(np.int32)[:,0,1] # prim node offset - - - - self.pto = self.prim.view(np.int32)[:,0,2] # prim tran offset - self.ppo = self.prim.view(np.int32)[:,0,3] # prim plan offset - - self.crn = self.pno[self.pnn>1] # node indices of compound roots - self.crn_subnum = self.node.view(np.int32)[self.crn,0,0] - self.crn_suboff = self.node.view(np.int32)[self.crn,0,1] - - - self.psb = self.prim.view(np.int32)[:,1,0] # prim sbtIndexOffset - self.plv = self.prim.view(np.int32)[:,1,1] # prim lvid/meshIdx - self.prx = self.prim.view(np.int32)[:,1,2] # prim ridx/repeatIdx - self.pix = self.prim.view(np.int32)[:,1,3] # prim idx - - self.pbb = self.prim.reshape(-1,16)[:,8:14] - - - self.snp = self.solid[:,1,0].view(np.int32) # solid numPrim - self.spo = self.solid[:,1,1].view(np.int32) # solid primOffset - self.sce = self.solid[:,2].view(np.float32) - - - - def simtrace_boundary_analysis(self, bn, KEY=os.environ.get("KEY", None) ): - """ - Group simtrace intersects according to their boundary indices - using groups specified by regexp matching boundary names. - - Typically would have a few hundred boundary names - in cf.bdn but potentially millions of simtrace intersects - in the bn array. - - :param bn: large array of boundary indices - :param KEY: color key OR None - :return cxtb,btab: - """ - d_qbn, d_anno = self.Dict_find_name_indices_re_match(self.bdn, self.bdn_config, self.bdn_config_note ) - btab = NameTable("btab", bn, self.bdn, d_qbn, KEY) - cxtb = GroupedNameTable("cxtb", bn, d_qbn, d_anno, self.bdn, lwid=100) - self.btab = btab - self.cxtb = cxtb - return cxtb, btab - - def simtrace_prim_analysis(self, pr, KEY=os.environ.get("KEY", None) ): - """ - Group simtrace intersects according to their primitive indices - using groups specified by regexp matching solid names of the prim. - - :param pr: large array of primitive indices - :param KEY: - :return cxtp, ptab: GroupedNameTable, NameTable - """ - d_qpr, d_anno = self.Dict_find_name_indices_re_match(self.primname, self.prn_config, self.prn_config_note ) - ptab = NameTable("ptab", pr, self.primname, d_qpr, KEY) - cxtp = GroupedNameTable("cxtp", pr, d_qpr, d_anno, self.primname, lwid=50) - self.ptab = ptab - self.cxtp = cxtp - return cxtp, ptab - - @classmethod - def Dict_find_name_indices_re_match(cls, names, _nameptn_dict, _namenote_dict ): - """ - :param names: array of names - :param _nameptn_dict: label keys, regexp pattern string values - :return d: dict with same label keys and arrays of matching names indices - - Names unmatched by the provided regexp values are - grouped within the key with a blank value if provided. - """ - d = {} - anno = {} - matched = np.array([], dtype=np.int64 ) - unmatched_k = None - for k, v in _nameptn_dict.items(): - if v == '': - unmatched_k = k # usaully grey - continue - pass - qnm, nn, label = cls.Find_name_indices_re_match(names, v) - matched = np.concatenate( (matched, qnm) ) - k_note = _namenote_dict.get(k,"") - lab = "%100s ## %s" % ( label, k_note ) - d[k] = qnm - anno[k] = lab - pass - - c = dict(matched=matched, all=np.arange(len(names))) - c['unmatched'] = np.where(np.isin(c['all'], c['matched'], invert=True ))[0] - if not unmatched_k is None: - d[unmatched_k] = c['unmatched'] - anno[unmatched_k] = "OTHER" - pass - return d, anno - - - @classmethod - def Find_name_indices_re_match(cls, names, _ptn): - """ - :param _ptn: name regexp string - :return qnm,nn,label: - - qnm - array of names array indices that match the pattern - nn - array of names matching the pattern - label - match key when the pattern string includes one eg (?