File indexing completed on 2026-04-09 07:48:50
0001
0002
0003 import os, logging, numpy as np
0004 log = logging.getLogger(__name__)
0005
0006 from opticks.ana.eget import efloatlist_, elookce_, elook_epsilon_, eint_
0007 from opticks.sysrap.sframe import sframe , X, Y, Z
0008
0009 SIZE = np.array([1280, 720])
0010 LEGEND = not "NOLEGEND" in os.environ
0011 GSPLOT = eint_("GSPLOT", "0")
0012
0013
0014
0015 MP = not "NOMP" in os.environ
0016 if MP:
0017 try:
0018 import matplotlib.pyplot as mp
0019 from matplotlib.patches import Circle, Rectangle, Ellipse
0020 except ImportError:
0021 mp = None
0022 pass
0023 else:
0024 mp = None
0025 pass
0026
0027
0028 PV = not "NOPV" in os.environ
0029 PVGRID = not "NOPVGRID" in os.environ
0030 if PV:
0031 try:
0032 import pyvista as pv
0033 themes = ["default", "dark", "paraview", "document" ]
0034 pv.set_plot_theme(themes[1])
0035 except ImportError:
0036 pv = None
0037 pass
0038 else:
0039 pv = None
0040 pass
0041
0042 from opticks.ana.pvplt import *
0043
0044
0045
0046 class SimtracePlot(object):
0047 def __init__(self, pl, feat, gs, frame, pos, outdir ):
0048 """
0049 :param pl: pyvista plotter instance, can be None
0050 :param feat: Feature instance
0051 :param gs: FrameGensteps instance
0052 :param frame: sframe instance
0053 :param pos: Positions instance
0054 :param outdir: str
0055
0056 ## hmm regarding annotation, what should come from remote and what local ?
0057
0058 XX,YY,ZZ
0059 lists of ordinates for drawing lines parallel to axes
0060
0061 """
0062 if not os.path.isdir(outdir):
0063 os.makedirs(outdir)
0064 pass
0065 self.pl = pl
0066 self.feat = feat
0067 self.gs = gs
0068 self.frame = frame
0069 self.pos = pos
0070 self.outdir = outdir
0071 self.pl = None
0072
0073 topline = os.environ.get("TOPLINE", "CSGOptiXSimtraceTest.py:PH")
0074 botline = os.environ.get("BOTLINE", "cxs")
0075 note = os.environ.get("NOTE", "")
0076 note1 = os.environ.get("NOTE1", "")
0077
0078 self.topline = topline
0079 self.botline = botline
0080 self.note = note
0081 self.note1 = note1
0082
0083 self.look = efloatlist_("LOOK", "0,0,0")
0084 self.look_ce = elookce_(extent=10)
0085
0086
0087 epsilon = self.frame.propagate_epsilon
0088 if epsilon == 0.: epsilon = 0.05
0089 self.look_epsilon = elook_epsilon_(epsilon)
0090
0091
0092 aa = {}
0093 aa[X] = efloatlist_("XX")
0094 aa[Y] = efloatlist_("YY")
0095 aa[Z] = efloatlist_("ZZ")
0096
0097 self.aa = aa
0098 self.sz = float(os.environ.get("SZ","1.0"))
0099
0100 log.info(" aa[X] %s " % str(self.aa[X]))
0101 log.info(" aa[Y] %s " % str(self.aa[Y]))
0102 log.info(" aa[Z] %s " % str(self.aa[Z]))
0103
0104 def outpath_(self, stem="positions", ptype="pvplt"):
0105 sisel = self.feat.sisel
0106 return os.path.join(self.outdir,"%s_%s_%s.png" % (stem, ptype, self.feat.name))
0107
0108 def positions_mpplt(self):
0109 axes = self.frame.axes
0110 if len(axes) == 2:
0111 self.positions_mpplt_2D(legend=LEGEND, gsplot=GSPLOT)
0112 else:
0113 log.info("mp skip 3D plotting as PV is so much better at that")
0114 pass
0115
0116 def positions_mpplt_2D(self, legend=True, gsplot=0):
0117 """
0118 (H,V) are the plotting axes
0119 (X,Y,Z) = (0,1,2) correspond to absolute axes which can be mapped to plotting axes in various ways
0120
0121 when Z is vertical lines of constant Z appear horizontal
0122 when Z is horizontal lines of constant Z appear vertical
0123 """
0124 upos = self.pos.upos
0125 ugsc = self.gs.ugsc
0126 lim = self.gs.lim
0127
0128 H,V = self.frame.axes
0129 _H,_V = self.frame.axlabels
0130 log.info(" frame.axes H:%s V:%s " % (_H, _V))
0131
0132 feat = self.feat
0133 sz = self.sz
0134 print("positions_mpplt feat.name %s " % feat.name )
0135
0136 igs = slice(None) if len(ugsc) > 1 else 0
0137
0138
0139
0140
0141
0142 fig, ax = self.frame.mp_subplots(mp)
0143
0144 self.ax = ax
0145 self.fig = fig
0146
0147 note = self.note
0148 note1 = self.note1
0149
0150 if len(note) > 0:
0151 mp.text(0.01, 0.99, note, horizontalalignment='left', verticalalignment='top', transform=ax.transAxes)
0152 pass
0153 if len(note1) > 0:
0154 mp.text(0.01, 0.95, note1, horizontalalignment='left', verticalalignment='top', transform=ax.transAxes)
0155 pass
0156
0157
0158
0159 for idesc in range(feat.unum):
0160 uval, selector, label, color, skip, msg = feat(idesc)
0161 if skip: continue
0162 pos = upos[selector]
0163
0164
0165
0166 log.debug(" feat.unum %d msg %s " % (feat.unum, msg ))
0167 ax.scatter( pos[:,H], pos[:,V], label=label, color=color, s=sz )
0168 pass
0169
0170
0171 mpplt_parallel_lines(ax, self.gs.lim, self.aa, self.frame.axes, linestyle="dashed" )
0172
0173 if hasattr(self, 'x_lpos'):
0174 x_lpos = self.x_lpos
0175 ax.scatter( x_lpos[:,H], x_lpos[:,V], label="x_lpos", s=10 )
0176 mpplt_add_contiguous_line_segments(ax, x_lpos, axes=self.frame.axes, linewidths=2)
0177 pass
0178 if hasattr(self, 't_spos'):
0179 t_spos = self.t_spos
0180 ax.scatter( t_spos[:,H], t_spos[:,V], label="t_spos", s=5 )
0181 pass
0182 if hasattr(self, 'simtrace_selection'):
0183 sts = self.simtrace_selection
0184 mpplt_simtrace_selection_line(ax, sts, axes=self.frame.axes, linewidths=2)
0185 pass
0186 if not self.look_ce is None:
0187 mpplt_ce_multiple(ax, self.look_ce, axes=self.frame.axes)
0188 pass
0189 if not self.look_epsilon is None:
0190 mpplt_ce(ax, self.look_epsilon, axes=self.frame.axes, colors="yellow" )
0191 pass
0192 label = "gs_center XZ"
0193 if gsplot > 0:
0194 ax.scatter( ugsc[igs, H], ugsc[igs,V], label=None, s=sz )
0195 pass
0196
0197
0198
0199
0200
0201
0202
0203
0204
0205
0206 if legend:
0207 ax.legend(loc="upper right", markerscale=4)
0208 ptype = "mpplt"
0209 else:
0210 ptype = "mpnky"
0211 pass
0212 if GUI:
0213 fig.show()
0214 pass
0215
0216
0217
0218 def positions_pvplt(self):
0219 axes = self.frame.axes
0220 if len(axes) == 2:
0221 self.positions_pvplt_2D()
0222 else:
0223 self.positions_pvplt_3D()
0224 pass
0225
0226 @classmethod
0227 def MakePVPlotter(cls):
0228 log.info("MakePVPlotter")
0229 pl = pv.Plotter(window_size=SIZE*2 )
0230 return pl
0231
0232 def get_pv_plotter(self):
0233 if self.pl is None:
0234 pl = self.MakePVPlotter()
0235 self.pl = pl
0236 else:
0237 pl = self.pl
0238 log.info("using preexisting plotter")
0239 pass
0240 return pl
0241
0242
0243 def positions_pvplt_3D(self):
0244 """
0245 Could try to reconstruct solid surface from the point cloud of intersects
0246 https://docs.pyvista.org/api/core/_autosummary/pyvista.PolyDataFilters.reconstruct_surface.html#pyvista.PolyDataFilters.reconstruct_surface
0247 """
0248 pass
0249 pl = self.get_pv_plotter()
0250
0251 feat = self.feat
0252 upos = self.pos.upos
0253
0254 log.info("feat.unum %d " % feat.unum)
0255
0256 for idesc in range(feat.unum):
0257 uval, selector, label, color, skip, msg = feat(idesc)
0258 if skip: continue
0259 pos = upos[selector]
0260 print(msg)
0261 pl.add_points( pos[:,:3], color=color, point_size=10 )
0262 pass
0263 pl.enable_eye_dome_lighting()
0264
0265
0266 pl.show_grid()
0267
0268
0269 def positions_pvplt_2D(self):
0270 """
0271 * actually better to use set_position reset=True after adding points to auto get into ballpark
0272
0273 * previously always starts really zoomed in, requiring two-finger upping to see the intersects
0274 * following hint from https://github.com/pyvista/pyvista/issues/863 now set an adhoc zoom factor
0275
0276 Positioning the eye with a simple global frame y-offset causes distortion
0277 and apparent untrue overlaps due to the tilt of the geometry.
0278 Need to apply the central transform to the gaze vector to get straight on view.
0279
0280 https://docs.pyvista.org/api/core/_autosummary/pyvista.Camera.zoom.html?highlight=zoom
0281
0282 In perspective mode, decrease the view angle by the specified factor.
0283
0284 In parallel mode, decrease the parallel scale by the specified factor.
0285 A value greater than 1 is a zoom-in, a value less than 1 is a zoom-out.
0286 """
0287
0288 lim = self.gs.lim
0289 ugsc = self.gs.ugsc
0290 upos = self.pos.upos
0291
0292 feat = self.feat
0293
0294 pl = self.get_pv_plotter()
0295
0296 pl.add_text(self.topline, position="upper_left")
0297 pl.add_text(self.botline, position="lower_left")
0298 pl.add_text(self.frame.thirdline, position="lower_right")
0299
0300 log.info("positions_pvplt_2D feat.name %s " % feat.name )
0301
0302 for idesc in range(feat.unum):
0303 uval, selector, label, color, skip, msg = feat(idesc)
0304 if skip: continue
0305 pos = upos[selector]
0306 log.info(msg)
0307 pl.add_points( pos[:,:3], color=color )
0308 pass
0309
0310 if hasattr(self, 'x_lpos'):
0311 pvplt_add_contiguous_line_segments(pl, self.x_lpos[:,:3], point_size=25 )
0312 pass
0313
0314 if hasattr(self, 'simtrace_selection'):
0315 sts = self.simtrace_selection
0316 pvplt_simtrace_selection_line(pl, sts)
0317 pass
0318
0319 if not self.look_ce is None:
0320 pvplt_ce_multiple(pl, self.look_ce, axes=self.frame.axes)
0321 pass
0322
0323 if not self.look_epsilon is None:
0324 pvplt_ce(pl, self.look_epsilon, axes=self.frame.axes, color="yellow" )
0325 pass
0326
0327 show_genstep_grid = len(self.frame.axes) == 2
0328 if show_genstep_grid and PVGRID:
0329 pl.add_points( ugsc[:,:3], color="white" )
0330 pass
0331
0332 pvplt_parallel_lines(pl, self.gs.lim, self.aa, self.frame.axes, self.look )
0333
0334 self.frame.pv_compose(pl, local=True)
0335
0336 cp = pl.show()
0337
0338 return cp
0339
0340
0341 def mp_show(self):
0342 """
0343 Fatal Python error: Segmentation fault
0344 """
0345 if hasattr(self, 'img'):
0346 mp.imshow(self.img)
0347 mp.show()
0348 pass
0349
0350
0351
0352 if __name__ == '__main__':
0353 logging.basicConfig(level=logging.INFO)
0354
0355