File indexing completed on 2026-04-09 07:48:49
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021 """
0022 prim.py
0023 =========
0024
0025 Loads a list of primitives from a GParts persisted directory.
0026 Used for debugging things such as missing transforms.
0027
0028 See: notes/issues/OKX4Test_partBuffer_difference.rst
0029
0030 TODO: consolidate ana/prim.py with dev/csg/GParts.py
0031
0032 """
0033 import sys, os, logging, numpy as np
0034 log = logging.getLogger(__name__)
0035 from opticks.sysrap.OpticksCSG import CSG_
0036 from opticks.ana.blib import BLib
0037 from opticks.ana.mesh import Mesh
0038
0039 from opticks.ana.shape import Shape
0040
0041 class Ellipsoid(Shape):pass
0042 class Tubs(Shape):pass
0043 class Torus(Shape):pass
0044 class Cons(Shape):pass
0045 class Hype(Shape):pass
0046 class Box(Shape):pass
0047
0048 class UnionSolid(Shape):pass
0049 class SubtractionSolid(Shape):pass
0050 class IntersectionSolid(Shape):pass
0051
0052
0053 class Part(object):
0054 """
0055 Parts are CSG constituents, aka nodes of the CSG trees that make up each solid
0056 """
0057 part_idx = 0
0058
0059 def __init__(self, part, trans, d, prim):
0060 """
0061 Part instances are created within the parent Prim instance
0062 by mapping this Part ctor over elements of the parts (nodes)
0063 and global transforms arrays.
0064
0065 :param part: single csg node of shape (4,4)
0066 :param trans: 1 or more transforms, shape (ntran,4,4)
0067 :param d: Dir instance
0068 """
0069
0070 sidx = d.sidx
0071 self.d = d
0072 self.prim = prim
0073 self.sidx = sidx
0074
0075 assert part.shape == (4,4), part
0076 assert trans.shape[1:] == (4,4), trans
0077 assert trans.shape[0] < 16, trans
0078 assert d.__class__.__name__ == 'Solid'
0079 ntran = trans.shape[0]
0080 assert ntran > 0
0081
0082 log.debug( "Part : trans.shape %s " % repr(trans.shape))
0083
0084
0085 f = part.view(np.float32)
0086 u = part.view(np.uint32)
0087
0088 fc = part.copy().view(np.float32)
0089 fc[1][2] = 0
0090
0091
0092 tc = u[2][3]
0093 tcn = CSG_.desc(tc)
0094 bnd = u[1][2]
0095
0096
0097 comp = np.signbit(f[3,3])
0098
0099 comp2 = u[3,3] >> 31
0100 assert comp == comp2
0101
0102
0103 gt = u[3][3] & 0x7fffffff
0104
0105 if tc in [CSG_.DIFFERENCE, CSG_.INTERSECTION, CSG_.UNION]:
0106 assert gt == 0, "operators are expected to not have a gtransform"
0107 elif tc == CSG_.ZERO:
0108 assert gt == 0, "zeros are not expected to have a gtransform"
0109 else:
0110 assert gt > 0, "primitives are expected to have a gtransform "
0111
0112
0113
0114 if not gt <= ntran:
0115 log.warn("1-based gt expected to be <= ntran (local index, not global) gt:%d ntran:%d " % (gt,ntran))
0116 pass
0117
0118 if ntran > 5:
0119 pass
0120
0121 pass
0122 pass
0123
0124
0125 self.f = f
0126 self.fc = fc
0127
0128 self.tc = tc
0129 self.tcn = tcn
0130 self.comp = comp
0131 self.gt = gt
0132
0133 self.bnd = bnd
0134 self.bname = d.blib.bname(bnd)
0135
0136 try:
0137 tran = trans[gt-1] if gt > 0 else np.eye(4)
0138 except IndexError:
0139 log.error("trans issue gt %s trans.shape %s " % ( gt, repr(trans.shape)))
0140 tran = np.eye(4)
0141 pass
0142
0143 self.tran = tran
0144
0145 self.idx = self.__class__.part_idx
0146 self.__class__.part_idx += 1
0147
0148
0149
0150 def __repr__(self):
0151 return " Part %1s%2s %2s %15s %3d %25s tz:%10.3f %s " % ( "!" if self.comp else " ", self.tc, self.gt, self.tcn, self.bnd, self.bname, self.tz, self.detail() )
0152
0153 def maxdiff(self, other):
0154 """
0155 :param other: Part instance
0156 :return float: max absolute difference between float param values of the CSG constituent part
0157 (actually the boundary index is excluded by comparing a copy and scrubbing that)
0158 """
0159 return np.max( np.abs(self.fc - other.fc) )
0160
0161 r = property(lambda self:self.f[0][3])
0162 tz = property(lambda self:self.tran[3][2])
0163
0164 xbox = property(lambda self:self.f[0][0])
0165 ybox = property(lambda self:self.f[0][1])
0166 zbox = property(lambda self:self.f[0][2])
0167
0168 r1co = property(lambda self:self.f[0][0])
0169 z1co = property(lambda self:self.f[0][1])
0170 r2co = property(lambda self:self.f[0][2])
0171 z2co = property(lambda self:self.f[0][3])
0172
0173 z1 = property(lambda self:self.f[1][0])
0174 z2 = property(lambda self:self.f[1][1])
0175 r1 = property(lambda self:self.f[0][2])
0176 r2 = property(lambda self:self.f[0][3])
0177 dz = property(lambda self:self.z2 - self.z1)
0178 dr = property(lambda self:self.r2 - self.r1)
0179
0180
0181 def as_shape(self, name, sc):
0182 if self.tc == CSG_.BOX3:
0183 sh = Box(name, [self.xbox/sc, self.zbox/sc ] )
0184 elif self.tc == CSG_.CYLINDER:
0185 sh = Tubs(name, [self.r/sc, abs(self.z1/sc) ] )
0186 elif self.tc == CSG_.TORUS:
0187 sh = Ellipsoid(name, [self.r/sc, self.r/sc ] )
0188 else:
0189 sh = None
0190 pass
0191 if not sh is None:
0192 sh.ltransform = [0, self.tz/sc ]
0193 pass
0194 return sh
0195
0196 def detail(self):
0197 tz = self.tz
0198 if self.tc == CSG_.ZSPHERE:
0199 msg = " r: %10.3f z1:%10.3f z2:%10.3f " % ( self.r, self.z1, self.z2 )
0200 elif self.tc == CSG_.SPHERE:
0201 msg = " r: %10.3f " % ( self.f[0][3] )
0202 elif self.tc == CSG_.CYLINDER:
0203 msg = " z1:%10.3f z2:%10.3f r :%10.3f z1+tz:%10.3f z2+tz:%10.3f" % ( self.z1, self.z2, self.r, self.z1 + tz, self.z2 + tz)
0204 elif self.tc == CSG_.CONE:
0205 msg = " z1:%10.3f z2:%10.3f r1:%10.3f r2:%10.3f z1+tz:%10.3f z2+tz:%10.3f" % ( self.z1co, self.z2co, self.r1co, self.r2co, self.z1co+tz, self.z2co+tz )
0206 elif self.tc == CSG_.BOX3:
0207 msg = " x:%10.3f y:%10.3f z:%10.3f " % ( self.xbox, self.ybox, self.zbox )
0208 else:
0209 msg = ""
0210 pass
0211 return msg
0212
0213
0214
0215 class Prim(object):
0216 """
0217
0218 In [21]: gp[2].primbuf
0219 Out[21]:
0220 array([[ 0, 1, 0, 0],
0221 [ 1, 7, 1, 0],
0222 [ 8, 7, 5, 0],
0223 [15, 7, 8, 0],
0224 [22, 1, 11, 0],
0225 [23, 7, 12, 0]], dtype=int32)
0226 | | | |
0227 partOffset | | |
0228 | | |
0229 numParts | |
0230 | |
0231 tranOffset |
0232 |
0233 planOffset
0234 """
0235 def __init__(self, primIdx, prim, d):
0236 """
0237 """
0238 assert primIdx > -1 and primIdx < 10000, primIdx
0239 assert prim.shape == (4,), "unexpected prim.shape %s " % repr(prim.shape)
0240
0241 self.sidx = d.sidx
0242 self.primIdx = primIdx
0243 self.prim = prim
0244
0245 idx = d.idxbuf[primIdx] if d.idxbuf is not None else -np.ones(4,dtype=np.uint32)
0246 lvIdx = idx[2]
0247
0248 self.idx = idx
0249 self.lvIdx = lvIdx
0250
0251 self.lvName = d.ma.idx2name.get(lvIdx, "-") if d.ma is not None else "--"
0252
0253 partOffset, numParts, tranOffset, planOffset = prim
0254 numTran = d.ntran[primIdx]
0255
0256 parts_ = d.partbuf[partOffset:partOffset+numParts]
0257 trans_ = d.tranbuf[tranOffset:tranOffset+numTran,0]
0258
0259 self.parts_ = parts_
0260 self.trans_ = trans_
0261
0262 self.parts = list(map(lambda _:Part(_,trans_,d, self), parts_))
0263
0264 self.partOffset = partOffset
0265 self.numParts= numParts
0266 self.numTran = numTran
0267 self.tranOffset = tranOffset
0268 self.planOffset = planOffset
0269 self.d = d
0270
0271 def maxdiff(self, other):
0272 """
0273 :return float: max difference over the constituent parts, from Part.maxdiff
0274 """
0275 assert len(self.parts) == len(other.parts)
0276 return max(map( lambda ab:ab[0].maxdiff(ab[1]), zip( self.parts, other.parts)))
0277
0278 def tr_maxdiff(self, other):
0279 """
0280 :param other: Prim instance to compare self with
0281 :return value: max absolute difference between the (numtran,4,4) elements
0282 """
0283 return np.max(np.abs(self.trans_ - other.trans_))
0284
0285 def __repr__(self):
0286 fmt = "sidx %2d primIdx %3s idx %30s lvIdx %3d lvName %30s partOffset %3s numParts %3s tranOffset %3s numTran %3s planOffset %3s "
0287 return fmt % (self.sidx, self.primIdx, str(self.idx), self.lvIdx, self.lvName, self.partOffset, self.numParts, self.tranOffset, self.numTran, self.planOffset )
0288
0289 def __str__(self):
0290 return "\n".join(["",repr(self)] + list(map(str,list(filter(lambda pt:pt.tc > 0, self.parts)))) + [repr(self.trans_)])
0291
0292
0293 class Solid(object):
0294 """
0295 This is Solid in the Opticks composite sense, formerly poorly named Dir
0296
0297 Handles the concatenated prims from $TMP/GParts/{0,1,2,...}
0298
0299 TODO: rename to "Prims"
0300 """
0301 def __init__(self, base, kd):
0302 """
0303 :param base: directory containing primBuffer.npy etc.. eg $TMP/GParts/0
0304
0305
0306 ::
0307
0308 In [5]: p.primBuf
0309 Out[5]:
0310 array([[0, 1, 0, 3],
0311 [1, 7, 1, 0]], dtype=uint32)
0312
0313 In [6]: p.partBuf.shape
0314 Out[6]: (8, 4, 4)
0315
0316 In [7]: p.partBuf
0317 Out[7]:
0318 array([[[ 0. , 0. , 0. , 1000. ],
0319 [ 0. , 0. , 0. , 0. ],
0320 [-1000.01 , -1000.01 , -1000.01 , 0. ],
0321 [ 1000.01 , 1000.01 , 1000.01 , 0. ]],
0322
0323 [[ 0. , 0. , 0. , 500. ],
0324 [ 0. , 0. , 0. , 0. ],
0325 [ -500.01 , -500.01 , -500.01 , 0. ],
0326 [ 500.01 , 500.01 , 500.01 , 0. ]],
0327
0328 """
0329 sidx = int(os.path.basename(base))
0330 self.sidx = sidx
0331 self.base = base
0332 self.blib = BLib(kd)
0333
0334 primbuf = np.load(os.path.join(base, "primBuffer.npy"))
0335 idxbuf = np.load(os.path.join(base, "idxBuffer.npy"))
0336 partbuf = np.load(os.path.join(base, "partBuffer.npy"))
0337 tranbuf = np.load(os.path.join(base, "tranBuffer.npy"))
0338
0339 assert len(primbuf) == len(idxbuf)
0340 assert len(tranbuf) <= len(partbuf)
0341
0342 tot_prim = len(primbuf)
0343 tot_tran = len(tranbuf)
0344 tot_part = len(partbuf)
0345
0346
0347 """
0348 1 -> 1
0349 10 11 -> 3 +2
0350 100 101 110 111 -> 7 +4
0351 1000 1001 1010 1011 1100 1101 1110 1111 -> 15 +8
0352 -> 31 +16
0353 """
0354 self.primbuf = primbuf
0355 self.partbuf = partbuf
0356 self.tranbuf = tranbuf
0357 self.idxbuf = idxbuf
0358
0359 log.info(str(self))
0360
0361
0362 ma = Mesh(kd)
0363
0364 lvidxs = idxbuf[:,1]
0365 lvns = list(map(lambda lvidx:ma.idx2name[lvidx],lvidxs))
0366 self.lvns = lvns
0367
0368 ntran = np.zeros( tot_prim, dtype=np.uint32)
0369 ntran[0:tot_prim-1] = primbuf[1:,2] - primbuf[:-1,2]
0370
0371 ntran_tot_excluding_last = ntran.sum()
0372 ntran_last = tot_tran - ntran_tot_excluding_last
0373 ntran[tot_prim-1] = ntran_last
0374
0375 self.ntran = ntran
0376
0377 self.ma = ma
0378 self.prims = self.get_prims()
0379
0380 def get_prims(self):
0381 """
0382 :return pp: python array of Prim instances deserialized from self.prim array
0383 """
0384 pp = []
0385
0386 tot_prim = len(self.primbuf)
0387 log.debug("get_prims sidx %d tot_prim %d " % (self.sidx, tot_prim))
0388
0389 for primIdx in range(tot_prim):
0390 prim_item = self.primbuf[primIdx]
0391 p = Prim(primIdx, prim_item, self)
0392 pp.append(p)
0393 pass
0394 return pp
0395
0396 def enumerate_prim_zip(self, other):
0397 """
0398 :return i_ab: (idx,(this_prim,other_prim)) i_ab[0]
0399 """
0400 assert len(self.prims) == len(other.prims)
0401 return enumerate(zip(self.prims, other.prims))
0402
0403 def where_discrepant_tr(self, other, cut=0.1):
0404 assert len(self.prims) == len(other.prims)
0405 return map(lambda i_ab:i_ab[0], filter(lambda i_ab:i_ab[1][0].tr_maxdiff(i_ab[1][1]) > cut , enumerate(zip(self.prims, other.prims)) ))
0406
0407 def where_discrepant_prims(self, other, cut=0.1):
0408 assert len(self.prims) == len(other.prims)
0409 return map(lambda i_ab:i_ab[0], filter(lambda i_ab:i_ab[1][0].maxdiff(i_ab[1][1]) > cut , enumerate(zip(self.prims, other.prims)) ))
0410
0411 def get_discrepant_prims(self, other, cut=0.1):
0412 assert len(self.prims) == len(other.prims)
0413 return filter(lambda i_ab:i_ab[1][0].maxdiff(i_ab[1][1]) > cut , enumerate(zip(self.prims, other.prims)) )
0414
0415 def __repr__(self):
0416 fmt = "Solid %d : %s : primbuf %s partbuf %s tranbuf %s idxbuf %s "
0417 return fmt % (self.sidx, self.base, repr(self.primbuf.shape), repr(self.partbuf.shape), repr(self.tranbuf.shape), repr(self.idxbuf.shape))
0418
0419
0420 if __name__ == '__main__':
0421 logging.basicConfig(level=logging.INFO)
0422
0423 ridx = 0
0424 ddir = os.path.expandvars(os.path.join("$TMP/GParts",str(ridx)))
0425 dir_ = sys.argv[1] if len(sys.argv) > 1 else ddir
0426 sli_ = sys.argv[2] if len(sys.argv) > 2 else "0:10"
0427 sli = slice(*map(int, sli_.split(":")))
0428
0429 if dir_ == ddir:
0430 log.warning("using hardcoded dir" ) ;
0431 pass
0432
0433 from opticks.ana.key import keydir
0434 kd = keydir(os.environ["OPTICKS_KEY"])
0435
0436 d = Dir(dir_, kd)
0437 print("Dir(dir_)", d)
0438
0439 pp = d.prims
0440 print("dump sliced prims from the dir slice %s " % repr(sli))
0441 for p in pp[sli]:
0442 print(p)
0443 pass
0444
0445