lavavu 1.9.0__cp39-cp39-macosx_10_9_x86_64.whl → 1.9.6__cp39-cp39-macosx_10_9_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
lavavu/control.py CHANGED
@@ -1689,13 +1689,26 @@ class _ControlFactory(object):
1689
1689
  """
1690
1690
  if not is_notebook():
1691
1691
  return
1692
- #Find matching viewer id, redisplay all that match
1692
+ from IPython.display import display,HTML,Javascript
1693
+ display(Javascript(self.redisplay_call()))
1694
+
1695
+ def redisplay_call(self):
1696
+ """Update the active viewer image if any
1697
+ Applies changes made in python to the viewer and forces a redisplay
1698
+ """
1699
+ #Find matching viewer id, redisplay first match
1700
+ ids = self.active_viewers()
1701
+ return ';'.join('_wi["{0}"].redisplay()'.format(i) for i in ids)
1702
+
1703
+ def active_viewers(self):
1704
+ """Return matching active viewer IDs
1705
+ """
1706
+ #Find matching viewer ids, all that match
1707
+ ids = []
1693
1708
  for idx,obj in enumerate(windows):
1694
1709
  if obj == self._target():
1695
- viewerid = winids[idx]
1696
- from IPython.display import display,HTML,Javascript
1697
- #display(Javascript('_wi["{0}"].redisplay();'.format(viewerid)))
1698
- display(HTML('<script>_wi["{0}"].redisplay();</script>'.format(viewerid)))
1710
+ ids.append(winids[idx])
1711
+ return ids
1699
1712
 
1700
1713
  def update(self):
1701
1714
  """Update the control values from current viewer data
lavavu/html/menu.js CHANGED
@@ -123,7 +123,12 @@ function menu_addctrls(menu, obj, viewer, onchange) {
123
123
  //Check if it has been set on the target object
124
124
  if (prop in obj) {
125
125
  //console.log(prop + " ==> " + JSON.stringify(viewer.dict[prop]));
126
- menu_addctrl(menu, obj, viewer, prop, onchange);
126
+ try {
127
+ //Catch errors and continue gracefully
128
+ menu_addctrl(menu, obj, viewer, prop, onchange);
129
+ } catch(e) {
130
+ console.log("Error adding control to menu: " + e);
131
+ }
127
132
 
128
133
  } else {
129
134
  //Save list of properties without controls
@@ -159,9 +164,9 @@ function menu_addctrls(menu, obj, viewer, onchange) {
159
164
 
160
165
  function menu_addcmaps(menu, obj, viewer, onchange) {
161
166
  //Colourmap editing menu
162
- if (viewer.cgui.prmenu) viewer.cgui.removeFolder(viewer.cgui.prmenu);
163
- if (viewer.cgui.cmenu) viewer.cgui.removeFolder(viewer.cgui.cmenu);
164
- if (viewer.cgui.pomenu) viewer.cgui.removeFolder(viewer.cgui.pomenu);
167
+ if (viewer.cgui.prmenu) viewer.cgui.remove(viewer.cgui.prmenu);
168
+ if (viewer.cgui.cmenu) viewer.cgui.remove(viewer.cgui.cmenu);
169
+ if (viewer.cgui.pomenu) viewer.cgui.remove(viewer.cgui.pomenu);
165
170
  viewer.cgui.prmenu = viewer.cgui.addFolder("Properties");
166
171
  viewer.cgui.cmenu = viewer.cgui.addFolder("Colours");
167
172
  viewer.cgui.pomenu = viewer.cgui.addFolder("Positions");
lavavu/html/webview.html CHANGED
@@ -27,7 +27,7 @@
27
27
 
28
28
  <input id="fileinput" type="file" style="visibility:hidden" onchange="useFileInput(this)" />
29
29
 
30
- <script async src="https://cdn.jsdelivr.net/gh/lavavu/lavavu.github.io@1.9.0/LavaVu-amalgamated.min.js"></script>
30
+ <script async src="https://cdn.jsdelivr.net/gh/lavavu/lavavu.github.io@1.9.6/LavaVu-amalgamated.min.js"></script>
31
31
  <!--script src="dat.gui.min.js"></script>
32
32
  <script src="OK-min.js"></script>
33
33
 
lavavu/lavavu.py CHANGED
@@ -53,6 +53,7 @@ import weakref
53
53
  import asyncio
54
54
  import quaternion as quat
55
55
  import platform
56
+ import matplotlib
56
57
 
57
58
  if sys.version_info[0] < 3:
58
59
  print("Python 3 required. LavaVu no longer supports Python 2.7.")
@@ -610,12 +611,12 @@ class Object(dict):
610
611
  return key in self.dict
611
612
 
612
613
  def __repr__(self):
613
- return str(self.ref)
614
+ return self.__str__()
614
615
 
615
616
  def __str__(self):
616
617
  #Default string representation
617
618
  self.parent._get() #Ensure in sync
618
- return '{\n' + str('\n'.join([' %s=%s' % (k,json.dumps(v)) for k,v in self.dict.items()])) + '\n}\n'
619
+ return '{\n' + str('\n'.join([' %s=%s' % (k,json.dumps(v)) for k,v in self.dict.items()])) + '\n}'
619
620
 
620
621
  #Interface for setting filters
621
622
  def include(self, *args, **kwargs):
@@ -1144,7 +1145,12 @@ class Object(dict):
1144
1145
  #Re-arrange to array of [r,g,b,a] values
1145
1146
  data = numpy.dstack((data[0],data[1],data[2]))
1146
1147
 
1147
- self._loadScalar(data, LavaVuPython.lucRGBAData)
1148
+ #Use the shape as dims for texture image data
1149
+ if len(data.shape) > 1:
1150
+ width, height = data.shape[1], data.shape[0]
1151
+ depth = 4 #4 channel RGBA
1152
+
1153
+ self._loadScalar(data, LavaVuPython.lucRGBAData, width, height, depth)
1148
1154
 
1149
1155
 
1150
1156
  def luminance(self, data):
@@ -1166,7 +1172,7 @@ class Object(dict):
1166
1172
 
1167
1173
  self._loadScalar(data, LavaVuPython.lucLuminanceData, width, height, depth)
1168
1174
 
1169
- def texture(self, data=None, flip=True, filter=2, bgr=False, label=""):
1175
+ def texture(self, data=None, flip=None, filter=2, bgr=False, label=""):
1170
1176
  """
1171
1177
  Load or clear texture data for object
1172
1178
 
@@ -1186,7 +1192,7 @@ class Object(dict):
1186
1192
  If not provided, will use the default/primary texture for the object
1187
1193
  flip : boolean
1188
1194
  flip the texture vertically after loading
1189
- (default is enabled as usually required for OpenGL but can be disabled)
1195
+ (default is to load value from "fliptexture" property)
1190
1196
  filter : int
1191
1197
  type of filtering, 0=None/nearest, 1=linear, 2=mipmap linear
1192
1198
  bgr : boolean
@@ -1196,6 +1202,11 @@ class Object(dict):
1196
1202
  #Clear texture
1197
1203
  self.parent.app.clearTexture(self.ref)
1198
1204
  return
1205
+ if flip is None:
1206
+ if "fliptexture" in self:
1207
+ flip = self["fliptexture"]
1208
+ else:
1209
+ flip = self.parent["fliptexture"]
1199
1210
  if isinstance(data, str):
1200
1211
  self.parent.app.setTexture(self.ref, data, flip, filter, bgr, label)
1201
1212
  return
@@ -1266,6 +1277,8 @@ class Object(dict):
1266
1277
  self.ref.colourMap = data.ref
1267
1278
  data = None
1268
1279
  else:
1280
+ if isinstance(data, matplotlib.colors.Colormap):
1281
+ data = matplotlib_cmap(data)
1269
1282
  if data is None:
1270
1283
  self.parent._get() #Ensure in sync
1271
1284
  cmid = self["colourmap"]
@@ -2734,7 +2747,7 @@ class Viewer(dict):
2734
2747
 
2735
2748
  """
2736
2749
 
2737
- def __init__(self, *args, resolution=None, binpath=None, context="default", port=8080, threads=False, **kwargs):
2750
+ def __init__(self, *args, resolution=None, binpath=None, context=None, port=8080, threads=False, **kwargs):
2738
2751
  """
2739
2752
  Create and init viewer instance
2740
2753
 
@@ -2771,7 +2784,9 @@ class Viewer(dict):
2771
2784
  self._collections = {}
2772
2785
  self.validate = True #Property validation flag
2773
2786
  self.recording = None
2774
- self.context = os.environ.get('LV_CONTEXT', context)
2787
+ self.context = context
2788
+ if self.context is None:
2789
+ self.context = os.environ.get('LV_CONTEXT', 'default')
2775
2790
 
2776
2791
  #Exit handler to clean up threads
2777
2792
  #(__del__ does not always seem to get called on termination)
@@ -3249,10 +3264,12 @@ class Viewer(dict):
3249
3264
  self.validate = self.state["properties"]["validate"]
3250
3265
  self._objects._sync()
3251
3266
 
3252
- def _set(self):
3267
+ def _set(self, reload=None):
3253
3268
  #Export state to lavavu
3254
3269
  #(include current object list state)
3255
3270
  #self.state["objects"] = [obj.dict for obj in self._objects.list]
3271
+ if reload is not None:
3272
+ self.state["reload"] = reload
3256
3273
  self.app.setState(json.dumps(self.state))
3257
3274
 
3258
3275
  def commands(self, cmds, queue=False):
@@ -3732,7 +3749,7 @@ class Viewer(dict):
3732
3749
  else:
3733
3750
  return c.tolist()
3734
3751
 
3735
- def texture(self, label, data=None, flip=True, filter=2, bgr=False):
3752
+ def texture(self, label, data=None, flip=None, filter=2, bgr=False):
3736
3753
  """
3737
3754
  Load or clear global/shared texture data
3738
3755
 
@@ -3751,7 +3768,7 @@ class Viewer(dict):
3751
3768
  Pass a string to load a texture from given filename
3752
3769
  flip : boolean
3753
3770
  flip the texture vertically after loading
3754
- (default is enabled as usually required for OpenGL but can be disabled)
3771
+ (default is to load value from "fliptexture" property)
3755
3772
  filter : int
3756
3773
  type of filtering, 0=None/nearest, 1=linear, 2=mipmap linear
3757
3774
  bgr : boolean
@@ -3761,6 +3778,8 @@ class Viewer(dict):
3761
3778
  #Clear texture
3762
3779
  self.app.clearTexture(None, label)
3763
3780
  return
3781
+ if flip is None:
3782
+ flip = self["fliptexture"]
3764
3783
  if isinstance(data, str):
3765
3784
  self.app.setTexture(None, data, flip, filter, bgr, label)
3766
3785
  return
@@ -4168,7 +4187,7 @@ class Viewer(dict):
4168
4187
  from IPython.display import display,HTML,Javascript
4169
4188
  display(Javascript(js + code))
4170
4189
 
4171
- def video(self, filename="", resolution=(0,0), fps=30, quality=0, encoder="h264", player=None, options={}, **kwargs):
4190
+ def video(self, filename="", resolution=(0,0), fps=30, quality=2, encoder="h264", embed=False, player=None, options={}, **kwargs):
4172
4191
  """
4173
4192
  Record and show the generated video inline within an ipython notebook.
4174
4193
 
@@ -4195,6 +4214,9 @@ class Viewer(dict):
4195
4214
  If omitted will use default settings, can fine tune settings in kwargs
4196
4215
  encoder : str
4197
4216
  Name of encoder to use, eg: "h264" (default), "mpeg"
4217
+ embed : bool
4218
+ Set to true to embed the video file rather than link to url
4219
+ Not recommended for large videos, default is False
4198
4220
  player : dict
4199
4221
  Args to pass to the player when the video is finished, eg:
4200
4222
  {"width" : 800, "height", 400, "params": "controls autoplay"}
@@ -4209,12 +4231,11 @@ class Viewer(dict):
4209
4231
  recorder : Video(object)
4210
4232
  Context manager object that controls the video recording
4211
4233
  """
4212
- return Video(self, filename, resolution, fps, quality, encoder, player, options, **kwargs)
4234
+ return Video(self, filename, resolution, fps, quality, encoder, embed, player, options, **kwargs)
4213
4235
 
4214
- def video_steps(self, filename="", start=0, end=0, fps=10, quality=1, resolution=(0,0), **kwargs):
4236
+ def video_steps(self, filename="", start=0, end=0, resolution=(0,0), fps=30, quality=2, encoder="h264", embed=False, player=None, options={}, **kwargs):
4237
+ my_func.__doc__
4215
4238
  """
4216
- TODO: Fix to use pyAV
4217
-
4218
4239
  Record a video of the model by looping through all time steps
4219
4240
 
4220
4241
  Shows the generated video inline within an ipython notebook.
@@ -4226,26 +4247,23 @@ class Viewer(dict):
4226
4247
 
4227
4248
  Parameters
4228
4249
  ----------
4229
- filename : str
4230
- Name of the file to save, if not provided a default will be used
4231
4250
  start : int
4232
4251
  First timestep to record, if not specified will use first available
4233
4252
  end : int
4234
4253
  Last timestep to record, if not specified will use last available
4235
- fps : int
4236
- Frames to output per second of video
4237
- quality : int
4238
- Encoding quality, 1=low(default), 2=medium, 3=high, higher quality reduces
4239
- encoding artifacts at cost of larger file size
4240
- resolution : list or tuple
4241
- Video resolution in pixels [x,y]
4242
- **kwargs :
4243
- Any additional keyword args will be passed to lavavu.player()
4254
+
4255
+ All other parameters same as video()
4244
4256
  """
4245
4257
 
4246
4258
  try:
4247
- fn = self.app.video(filename, fps, resolution[0], resolution[1], start, end, quality, **kwargs)
4248
- player(fn, **kwargs)
4259
+ steps = self.steps
4260
+ if end == 0: end = len(steps)
4261
+ steps = steps[start:end]
4262
+ from tqdm.notebook import tqdm
4263
+ with Video(self, filename, resolution, fps, quality, encoder, embed, player, options, **kwargs):
4264
+ for s in tqdm(steps, desc='Rendering loop'):
4265
+ self.timestep(s)
4266
+ self.render()
4249
4267
  except (Exception) as e:
4250
4268
  print("Video output error: " + str(e))
4251
4269
  pass
@@ -4461,10 +4479,10 @@ class Viewer(dict):
4461
4479
  for key in ["translate", "rotate", "xyzrotate", "fov", "focus"]:
4462
4480
  if key in src:
4463
4481
  dst[key] = copy.copy(src[key])
4464
- #Round down arrays to max 3 decimal places
4482
+ #Round down arrays to max 6 decimal places
4465
4483
  try:
4466
4484
  for r in range(len(dst[key])):
4467
- dst[key][r] = round(dst[key][r], 3)
4485
+ dst[key][r] = round(dst[key][r], 6)
4468
4486
  except:
4469
4487
  #Not a list/array
4470
4488
  pass
@@ -4478,7 +4496,7 @@ class Viewer(dict):
4478
4496
  #Set
4479
4497
  if data is not None:
4480
4498
  copyview(self.state["views"][0], data)
4481
- self._set()
4499
+ self._set(reload=False)
4482
4500
 
4483
4501
  #Return
4484
4502
  return vdat
@@ -4501,7 +4519,7 @@ class Viewer(dict):
4501
4519
  if at is None:
4502
4520
  at = self["focus"]
4503
4521
  else:
4504
- self["focus"] = at
4522
+ self.focus(*at)
4505
4523
 
4506
4524
  # Default to Y-axis up vector
4507
4525
  if up is None:
@@ -4545,7 +4563,7 @@ class Viewer(dict):
4545
4563
  res = res / numpy.linalg.norm(res)
4546
4564
  final[key] = res.tolist()
4547
4565
 
4548
- self.camera(final)
4566
+ self.camera(final, quiet=True)
4549
4567
 
4550
4568
  def flyto(self, pos, steps, stop=False, callback=None):
4551
4569
  # Fly using current camera orientation as start point
@@ -4553,7 +4571,7 @@ class Viewer(dict):
4553
4571
  return self.fly(pos0, pos, steps, stop, callback)
4554
4572
 
4555
4573
  def fly(self, pos0, pos1, steps, stop=False, callback=None):
4556
- self.camera(pos0)
4574
+ self.camera(pos0, quiet=True)
4557
4575
  self.render()
4558
4576
 
4559
4577
  for i in range(steps):
@@ -5390,49 +5408,44 @@ def player(filename, params="controls autoplay loop", **kwargs):
5390
5408
 
5391
5409
  if is_notebook():
5392
5410
  from IPython.display import display,HTML,Video,Javascript
5393
- import uuid
5394
- vid = 'video_' + str(uuid.uuid4())[:8]
5395
-
5396
- '''
5397
- def get_fn(filename):
5398
- import os
5399
- #This is unreliable, path incorrect on NCI
5400
- nbpath = os.getenv('JPY_SESSION_NAME')
5401
- if nbpath is not None:
5402
- import os.path
5403
- relpath = os.path.relpath(os.getcwd(), start=os.path.dirname(nbpath))
5404
- return relpath + '/' + filename
5405
- return filename
5406
- ''';
5407
-
5408
- #Embed player
5409
- filename = os.path.relpath(filename)
5410
- display(Video(url=filename, html_attributes=f"id='{vid}' " + params, **kwargs))
5411
-
5412
- #Add download link
5413
- display(HTML(f'<a id="link_{vid}" href="{filename}" download>Download Video</a>'))
5414
5411
 
5415
5412
  # Fallback - replace url on gadi and similar jupyterhub installs with
5416
5413
  # fixed working directory that doesn't match notebook dir
5417
5414
  # check the video tag url and remove subpath on 404 error
5418
- display(Javascript(f"""
5419
- let el = document.getElementById('{vid}');
5420
- let url = el.src;
5421
- fetch(url, {{method: 'HEAD'}}).then(response=>{{
5422
- if(response.status == 404) {{
5423
- console.log("Bad video url: " + url);
5424
- let toppath = "/files/home/"
5425
- let baseurl = url.substring(0, url.indexOf(toppath)+toppath.length);
5426
- let endurl = url.substring(url.indexOf("{filename}"));
5427
- let fixed = baseurl + endurl;
5428
- console.log("Replaced video url: " + fixed);
5429
- el.src = fixed;
5430
- //Also fix download link
5431
- document.getElementById('link_{vid}').href = fixed;
5432
- }}
5433
- }});
5434
- """))
5435
-
5415
+ onerror = """{
5416
+ let url = this.src;
5417
+ console.log('Error in video url: ' + url);
5418
+ var urlp = new window.URL(url);
5419
+ let toppath = '/files/home/';
5420
+ let startidx = urlp.pathname.indexOf(toppath);
5421
+ if (startidx < 0) {
5422
+ toppath = '/files/';
5423
+ startidx = urlp.pathname.indexOf(toppath);
5424
+ }
5425
+ if (startidx >= 0) {
5426
+ let filename = urlp.pathname.split('/').pop();
5427
+ let base = urlp.pathname.substring(0, startidx+toppath.length);
5428
+ urlp.pathname = base + filename;
5429
+ if (url != urlp.href) {
5430
+ console.log('Replaced video url: ' + urlp.href);
5431
+ this.src = urlp.href;
5432
+ //Also fix download link
5433
+ let link = document.getElementById('link_' + this.id);
5434
+ if (link) link.href = urlp.href;
5435
+ }
5436
+ }
5437
+ }"""
5438
+
5439
+ if "embed" in kwargs:
5440
+ #Not needed if embedding
5441
+ onerror = ""
5442
+
5443
+ #Display player and download link
5444
+ import uuid
5445
+ vid = 'video_' + str(uuid.uuid4())[:8]
5446
+ filename = os.path.relpath(filename)
5447
+ display(Video(filename=filename, html_attributes=f'id="{vid}" onerror="{onerror}" ' + params, **kwargs),
5448
+ HTML(f'<a id="link_{vid}" href="{filename}" download>Download Video</a>'))
5436
5449
 
5437
5450
  #Class for managing video animation recording
5438
5451
  class Video(object):
@@ -5452,7 +5465,7 @@ class Video(object):
5452
5465
  ... lv.rotate('y', 10) # doctest: +SKIP
5453
5466
  ... lv.render() # doctest: +SKIP
5454
5467
  """
5455
- def __init__(self, viewer=None, filename="", resolution=(0,0), framerate=30, quality=0, encoder="h264", player=None, options={}, **kwargs):
5468
+ def __init__(self, viewer, filename="output.mp4", resolution=(0,0), framerate=30, quality=2, encoder="h264", embed=False, player=None, options={}, **kwargs):
5456
5469
  """
5457
5470
  Record and show the generated video inline within an ipython notebook.
5458
5471
 
@@ -5472,13 +5485,16 @@ class Video(object):
5472
5485
  fps : int
5473
5486
  Frames to output per second of video
5474
5487
  quality : int
5475
- Encoding quality, 1=low, 2=medium, 3=high, higher quality reduces
5488
+ Encoding quality, 1=low, 2=medium(default), 3=high, higher quality reduces
5476
5489
  encoding artifacts at cost of larger file size
5477
5490
  If omitted will use default settings, can fine tune settings in kwargs
5478
5491
  resolution : list or tuple
5479
5492
  Video resolution in pixels [x,y]
5480
5493
  encoder : str
5481
5494
  Name of encoder to use, eg: "h264" (default), "mpeg"
5495
+ embed : bool
5496
+ Set to true to embed the video file rather than link to url
5497
+ Not recommended for large videos, default is False
5482
5498
  player : dict
5483
5499
  Args to pass to the player when the video is finished, eg:
5484
5500
  {"width" : 800, "height", 400, "params": "controls autoplay"}
@@ -5491,21 +5507,30 @@ class Video(object):
5491
5507
  if av is None:
5492
5508
  raise(ImportError("Video output not supported without pyAV - pip install av"))
5493
5509
  return
5494
- self.resolution = resolution
5495
- if self.resolution[0] == 0:
5496
- self.resolution = viewer.width
5497
- if self.resolution[1] == 0:
5498
- self.resolution = viewer.height
5510
+ self.resolution = list(resolution)
5511
+ if self.resolution[0] == 0 or self.resolution[1] == 0:
5512
+ self.resolution = (viewer.app.viewer.width, viewer.app.viewer.height)
5513
+ #Ensure resolution values are even or encoder fails
5514
+ self.resolution = (2 * int(self.resolution[0]//2), 2 * int(self.resolution[1] // 2))
5499
5515
  self.framerate = framerate
5500
5516
  self.quality = quality
5501
5517
  self.viewer = viewer
5502
5518
  self.filename = filename
5503
- if len(self.filename) == 0:
5504
- self.filename = "lavavu.mp4"
5505
5519
  self.player = player
5506
5520
  if self.player is None:
5507
- #Default player is half output resolution
5508
- self.player = {"width": self.resolution[0] // 2, "height": self.resolution[1] // 2}
5521
+ #Default player is half output resolution, unless < 900 then full
5522
+ if "width" in kwargs or "height" in kwargs or "params" in kwargs:
5523
+ #Back compatibility with old args
5524
+ self.player = {}
5525
+ self.player.update(kwargs)
5526
+ kwargs = {}
5527
+ elif self.resolution[0] > 900:
5528
+ #Larger output res - default to half size player
5529
+ self.player = {"width": self.resolution[0]//2, "height": self.resolution[1]//2}
5530
+ else:
5531
+ self.player = {}
5532
+ if embed:
5533
+ self.player["embed"] = True
5509
5534
  self.encoder = encoder
5510
5535
  self.options = options
5511
5536
  #Also include extra args
@@ -5603,6 +5628,7 @@ class Video(object):
5603
5628
  self.container.close()
5604
5629
  self.container = None
5605
5630
  self.stream = None
5631
+ self.viewer.recording = None
5606
5632
 
5607
5633
  def write(self, image):
5608
5634
  """
@@ -5630,9 +5656,8 @@ class Video(object):
5630
5656
 
5631
5657
  def play(self):
5632
5658
  """
5633
- Show the video in an inline player if in an interative notebook
5659
+ Show the video in an inline player if in an interactive notebook
5634
5660
  """
5635
- print(self.player)
5636
5661
  player(self.filename, **self.player)
5637
5662
 
5638
5663
  def __enter__(self):
lavavu/tracers.py CHANGED
@@ -1,124 +1,194 @@
1
1
  """
2
- Warning! EXPERIMENTAL:
3
- these features and functions are under development, will have bugs,
4
- and may be heavily modified in the future
5
-
6
2
  Tracer particles in a vector field
7
- Uses a KDTree to find nearest vector to advect the particles
8
3
 
9
- - Requires scipy.spatial
4
+ - Requires scipy.interpolate
10
5
  """
11
6
  import numpy
12
7
  import os
13
8
  import sys
14
9
  import random
15
- import math
16
- from scipy import spatial
17
-
18
- #Necessary? for large trees, detect?
19
- #sys.setrecursionlimit(10000)
10
+ from scipy.interpolate import RegularGridInterpolator
20
11
 
21
- class TracerState(object):
22
- def __init__(self, verts, N=5000):
23
- self.tree = spatial.cKDTree(verts)
24
- self.tracers = None
25
- self.steps = [0]*N
26
- self.values = None
27
- self.positions = None
28
- self.velocities = None
29
-
30
- def trace_particles(state, verts, vecs, N=5000, limit=0.5, speed=1.0, noise=0.0, height=None):
12
+ def random_particles(count, lowerbound=[0,0,0], upperbound=[1,1,1], dims=3):
31
13
  """
32
- Take a list of tracer vertices and matching velocity grid points (verts) & vectors (vecs)
33
- For each tracer
34
-
35
- - find the nearest velocity grid point
36
- - if within max dist: Multiply position by velocity vector
37
- - otherwise: Generate a new start position for tracer
38
-
39
- Parameters
40
- ----------
41
- state : TracerState
42
- Object returned from first call, pass None on first pass
43
- verts : array or list
44
- vertices of the vector field
45
- vecs : array or list
46
- vector values of the vector field
47
- N : int
48
- Number of particles to seed
49
- limit : float
50
- Distance limit over which tracers are not connected,
51
- For example if using a periodic boundary, setting limit to
52
- half the bounding box size will prevent tracer lines being
53
- connected when passing through the boundary
54
- speed : float
55
- Speed multiplier, scaling factor for the velocity taken from the vector values
56
- noise : float
57
- A noise factor, if set a random value is generated, multiplied by noise factor
58
- and added to each new position
59
- height : float
60
- A fixed height value, all positions will be given this height as their Z component
61
-
62
- Returns
63
- -------
64
- TracerState
65
- Object to hold the tracer state and track particles
66
- Pass this as first paramter on subsequent calls
14
+ Return an array of *count* 3d vertices of random particle positions
15
+ Minimum and maximum values defined by lowerbound and upperbound
67
16
  """
17
+ p = [None] * dims
18
+ for c in range(dims):
19
+ if lowerbound[c] == upperbound[c]:
20
+ p[c] = numpy.zeros(shape=(count)) + lowerbound[c]
21
+ else:
22
+ p[c] = numpy.random.uniform(low=lowerbound[c], high=upperbound[c], size=count)
23
+
24
+ return numpy.stack(p).T
68
25
 
69
- #KDstate.tree for finding nearest velocity grid point
70
- if state is None:
71
- state = TracerState(verts, N)
72
-
73
- lastid = 0
74
- def rand_vert():
75
- #Get a random velocity grid point
76
- lastid = random.randint(0, len(verts)-1)
77
- pos = verts[lastid]
78
- #Generate some random noise to offset
79
- noise3 = numpy.array((0.,0.,0.))
80
- if noise > 0.0:
81
- noise3 = numpy.random.rand(3) * noise
82
- #Fixed height?
83
- if height:
84
- noise3[2] = height
85
- #Return the sum
86
- return pos + noise3
87
-
88
- if state.positions is None:
89
- state.positions = numpy.zeros(shape=(N,3))
90
- state.velocities = numpy.zeros(shape=(N,3))
91
- state.values = numpy.zeros(shape=(N))
92
- for i in range(N):
93
- state.positions[i] = rand_vert()
94
- state.steps[i] = 0
95
- state.values[i] = numpy.linalg.norm(vecs[lastid])
96
-
97
- #Query all tracer state.positions
98
- q = state.tree.query(state.positions, k=1)
99
- for r in range(len(q[0])):
100
- #print("result, distance, point")
101
- #print(r, q[0][r], state.tree.data[q[1][r]], state.positions[r])
102
-
103
- #Increasing random chance as steps exceed 5 of a new start pos
104
- if random.randint(0,state.steps[r]) > 5:
105
- #Pick a new random grid vertex to start from
106
- #(Must be farther away than distance criteria)
107
- old = numpy.array(state.positions[r])
108
- while True:
109
- state.positions[r] = rand_vert()
110
- if numpy.linalg.norm(state.positions[r]-old) > limit:
111
- break
112
- state.steps[r] = 0
113
- state.values[r] = numpy.linalg.norm(vecs[lastid])
26
+ class Tracers():
27
+ def __init__(self, grid, count=1000, lowerbound=None, upperbound=None, limit=None, age=4, respawn_chance=0.2, speed_multiply=1.0, height=0.0, viewer=None):
28
+ """
29
+ Seed random particles into a vector field and trace their positions
30
+
31
+ Parameters
32
+ ----------
33
+ grid : list of coord arrays for each dimension as expected by RegularGridInterpolator,
34
+ or a numpy array of 2d or 3d vertices, which will be converted before being sent to the interpolator
35
+ Object returned from first call, pass None on first pass
36
+ count : int
37
+ Number of particles to seed and track
38
+ lowerbound : optional minimum vertex point defining particle bounding box,
39
+ if not provided will be taken from grid lower corner
40
+ upperbound : optional maximum vertex point defining particle bounding box,
41
+ if not provided will be taken from grid upper corner
42
+ limit : float
43
+ Distance limit over which tracers are not connected,
44
+ For example if using a periodic boundary, setting limit to
45
+ half the bounding box size will prevent tracer lines being
46
+ connected when passing through the boundary
47
+ age : int
48
+ Minimum particle age in steps after which particle can be deleted and respawned, defaults to 4
49
+ respawn : float
50
+ Probability of respawning, after age reached, default 0.2 ==> 1 in 5 chance of deletion
51
+ speed_multiply : float
52
+ Speed multiplier, scaling factor for the velocity taken from the vector values
53
+ height : float
54
+ A fixed height value, all positions will be given this height as their Z component
55
+ viewer : lavavu.Viewer
56
+ Viewer object for plotting functions
57
+ """
58
+ if len(grid) == 2:
59
+ self.gridx = grid[0]
60
+ self.gridy = grid[1]
61
+ self.gridz = numpy.array((height, height))
62
+ self.dims = 2
63
+ elif len(grid) == 3:
64
+ self.gridx = grid[0]
65
+ self.gridy = grid[1]
66
+ self.gridz = grid[2]
67
+ self.dims = 3
68
+ elif isinstance(grid, numpy.ndarray) and grid.shape[1] == 3:
69
+ self.gridx = grid[::,0]
70
+ self.gridy = grid[::,1]
71
+ self.gridz = grid[::,2]
72
+ self.dims = 3
73
+ elif isinstance(grid, numpy.ndarray) and grid.shape[1] == 2:
74
+ self.gridx = grid[::,0]
75
+ self.gridy = grid[::,1]
76
+ self.gridz = numpy.array((height, height))
77
+ self.dims = 2
114
78
  else:
115
- #Index of nearest grid point is in q[1][r]
116
- #Lookup vector at this index, multiply by position to get delta and add
117
- state.velocities[r] = vecs[q[1][r]] #Store velocity
118
- state.positions[r] += speed * vecs[q[1][r]]
119
- #Increment step tracking
120
- state.steps[r] += 1
121
- state.values[r] = numpy.linalg.norm(vecs[q[1][r]])
122
-
123
- return state
79
+ raise(ValueError('Grid needs to be array of 2d/3d vertices, or arrays of vertex coords (x, y, [z])'))
80
+
81
+ self.count = count
82
+ if lowerbound is None:
83
+ lowerbound = (self.gridx[0], self.gridy[0], self.gridz[0])
84
+ if upperbound is None:
85
+ upperbound = (self.gridx[-1], self.gridy[-1], self.gridz[-1])
86
+ self.positions = random_particles(self.count, lowerbound, upperbound, self.dims)
87
+ self.old_pos = numpy.zeros_like(self.positions)
88
+ self.lowerbound = lowerbound
89
+ self.upperbound = upperbound
90
+ self.velocities = None
91
+ self.steps = [0]*count
92
+ self.speed = numpy.zeros(shape=(count))
93
+ self.ages = numpy.zeros(shape=(count))
94
+ self.interp = None
95
+ if limit is None:
96
+ limit = 0.1 * (abs(self.gridx[-1] - self.gridx[0]) + abs(self.gridy[-1] - self.gridy[0]))
97
+ self.limit = limit
98
+ self.age = age
99
+ self.respawn_chance = respawn_chance
100
+ self.speed_multiply = speed_multiply
101
+ self.height = height
102
+
103
+ self.lv = viewer
104
+ self.points = None
105
+ self.arrows = None
106
+ self.tracers = None
107
+
108
+
109
+ def respawn(self, r):
110
+ #Dead or out of bounds particle, start at new position
111
+ #Loop until new position further from current position than limit
112
+ old_pos = self.positions[r]
113
+ pos = numpy.array([0.] * self.dims)
114
+ for i in range(10):
115
+ pos = random_particles(1, self.lowerbound, self.upperbound, self.dims)
116
+ dist = numpy.linalg.norm(old_pos - pos)
117
+ if dist > self.limit*1.01:
118
+ break
119
+
120
+ self.ages[r] = 0
121
+ self.positions[r] = pos
122
+
123
+ def update(self, vectors=None):
124
+ #Interpolate velocity at all positions,
125
+ #If vectors not passed, will use previous values
126
+ if vectors is not None:
127
+ if self.dims == 2:
128
+ self.interp = RegularGridInterpolator((self.gridx, self.gridy), vectors, bounds_error=False, fill_value=0.0)
129
+ else:
130
+ self.interp = RegularGridInterpolator((self.gridx, self.gridy, self.gridz), vectors, bounds_error=False, fill_value=0.0)
131
+
132
+ if self.interp is None:
133
+ raise(ValueError("No velocity grid, must pass vectors for first call of update()"))
134
+
135
+ self.velocities = self.interp(self.positions)
136
+ self.old_pos = numpy.copy(self.positions)
137
+
138
+ for r in range(len(self.velocities)):
139
+ #Lookup velocity at this index, multiply by position to get delta and add
140
+ self.speed[r] = numpy.linalg.norm(self.velocities[r])
141
+ if numpy.isnan(self.speed[r]): self.speed[r] = 0.0
142
+ if self.speed[r] == 0.0: #numpy.any(numpy.isinf(self.old_pos[r])) or numpy.any(numpy.isinf(self.positions[r])):
143
+ self.respawn(r)
144
+ else:
145
+ self.positions[r] = self.positions[r] + self.speed_multiply * self.velocities[r]
146
+ self.ages[r] += 1
147
+
148
+ #Bounds checks
149
+ #Chance of killing particle when over age, default 1 in 5 (0.2)
150
+ if (any(self.positions[r] < self.lowerbound[0:self.dims]) or any(self.positions[r] > self.upperbound[0:self.dims])
151
+ or (self.ages[r] > self.age and numpy.random.uniform() <= self.respawn_chance)):
152
+ #if r < 20: print("Kill", r, self.speed[r], numpy.isnan(self.speed[r])) # [0] == numpy.nan)
153
+ #self.positions[r] = numpy.array([numpy.inf] * self.dims)
154
+ #self.positions[r] = numpy.array([numpy.nan] * self.dims)
155
+ self.respawn(r)
156
+ self.velocities[r] = numpy.array([0.0] * self.dims)
157
+ self.speed[r] = 0.0
158
+
159
+ if self.lv:
160
+ positions = self.positions
161
+ if self.dims == 2 and self.height != 0:
162
+ #Convert to 3d and set z coord to height
163
+ shape = list(positions.shape)
164
+ shape[-1] = 3
165
+ positions = numpy.zeros(shape)
166
+ positions[::,0:2] = self.positions
167
+ positions[::,2] = numpy.array([self.height] * shape[0])
168
+ if self.points:
169
+ self.points.vertices(positions)
170
+ if len(self.points["colourmap"]):
171
+ self.points.values(self.speed)
172
+ if self.arrows:
173
+ self.arrows.vectors(self.velocities)
174
+ self.arrows.vertices(positions)
175
+ if len(self.arrows["colourmap"]):
176
+ self.arrows.values(self.speed)
177
+
178
+ if self.tracers:
179
+ self.tracers.vertices(positions)
180
+ if len(self.tracers["colourmap"]):
181
+ self.tracers.values(self.speed)
182
+
183
+ def plot_points(self, **kwargs):
184
+ if self.lv is not None and self.points is None:
185
+ self.points = self.lv.points('tracer_points', **kwargs)
186
+
187
+ def plot_arrows(self, **kwargs):
188
+ if self.lv is not None and self.arrows is None:
189
+ self.arrows = self.lv.vectors('tracer_arrows', **kwargs)
190
+
191
+ def plot_tracers(self, **kwargs):
192
+ if self.lv is not None and self.tracers is None:
193
+ self.tracers = self.lv.tracers('tracers', dims=self.count, limit=self.limit, **kwargs)
124
194
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: lavavu
3
- Version: 1.9.0
3
+ Version: 1.9.6
4
4
  Summary: Python interface to LavaVu OpenGL 3D scientific visualisation utilities
5
5
  Author-email: Owen Kaluza <owen@kaluza.id.au>
6
6
  License: ### Licensing
@@ -215,14 +215,16 @@ License-File: LICENSE.md
215
215
  Requires-Dist: numpy>=1.18
216
216
  Requires-Dist: aiohttp
217
217
  Requires-Dist: jupyter_server_proxy
218
+ Requires-Dist: matplotlib
218
219
  Requires-Dist: numpy-quaternion
220
+ Dynamic: license-file
219
221
 
220
222
  ![# logo](http://owen.kaluza.id.au/Slides/2017-08-15/LavaVu.png)
221
223
 
222
224
  [![Build Status](https://github.com/lavavu/LavaVu/workflows/Test/badge.svg)](https://github.com/lavavu/LavaVu/actions?query=workflow:Test)
223
225
  [![Deploy Status](https://github.com/lavavu/LavaVu/workflows/Deploy/badge.svg?branch=1.7.3)](https://github.com/lavavu/LavaVu/actions?query=workflow:Deploy)
224
226
  [![DOI](https://zenodo.org/badge/45163055.svg)](https://zenodo.org/badge/latestdoi/45163055)
225
- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/lavavu/LavaVu/1.9.0)
227
+ [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/lavavu/LavaVu/1.9.6)
226
228
 
227
229
  A scientific visualisation tool with a python interface for fast and flexible visual analysis.
228
230
 
@@ -230,11 +232,12 @@ Documentation available here [LavaVu Documentation](https://lavavu.github.io/Doc
230
232
 
231
233
  ![examplevis](http://owen.kaluza.id.au/Slides/2017-08-15/combined.png)
232
234
 
233
- LavaVu development is supported by the [Monash Immersive Visualisation Plaform](http://monash.edu.au/mivp) and the Simulation, Analysis & Modelling component of the [NCRIS AuScope](http://www.auscope.org.au/ncris/) capability.
235
+ LavaVu development is supported by [ACCESS-NRI](https://www.access-nri.org.au/).
236
+ Prior development was funded by the Monash Immersive Visualisation Plaform at [Monash eResearch](https://www.monash.edu/researchinfrastructure/eresearch) and the Simulation, Analysis & Modelling component of the [NCRIS AuScope](http://www.auscope.org.au/ncris/) capability.
234
237
 
235
238
  The acronym stands for: lightweight, automatable visualisation and analysis viewing utility, but "lava" is also a reference to its primary application as a viewer for geophysical simulations. It was also chosen to be unique enough to find the repository with google.
236
239
 
237
- The project started in the gLucifer<sup>1</sup> framework for visualising geodynamics simulations. The OpenGL visualisation module was separated from the simulation and sampling libraries and became a more general purpose visualisation tool. gLucifer continues as a set of sampling tools for Underworld simulations as part of the [Underworld2](https://github.com/underworldcode/underworld2/) code. LavaVu provides the rendering library for creating 2d and 3d visualisations to view this sampled data, inline within interactive IPython notebooks and offline through saved visualisation databases and images/movies.
240
+ The project started as a replacement rendering library for the gLucifer<sup>1</sup> framework, visualising geodynamics simulations. The new OpenGL visualisation code was re-implemented as a more general purpose visualisation tool. gLucifer continues as a set of sampling tools for Underworld simulations as part of the [Underworld2](https://github.com/underworldcode/underworld2/) code. LavaVu provides the rendering library for creating 2d and 3d visualisations to view this sampled data, inline within interactive Jupyter notebooks and offline through saved visualisation databases and images/movies.
238
241
 
239
242
  As a standalone tool it is a scriptable 3D visualisation tool capable of producing publication quality high res images and video output from time varying data sets along with HTML5 3D visualisations in WebGL.
240
243
  Rendering features include correctly and efficiently rendering large numbers of opaque and transparent points and surfaces and volume rendering by GPU ray-marching. There are also features for drawing vector fields and tracers (streamlines).
@@ -242,12 +245,11 @@ Rendering features include correctly and efficiently rendering large numbers of
242
245
  Control is via python and a set of simple verbose scripting commands along with mouse/keyboard interaction.
243
246
  GUI components can be generated for use from a web browser via the python "control" module and a built in web server.
244
247
 
248
+ Widgets for interactive use in the Jupyter notebook environment allow use for remote visualisation, eg: on supercomputing environments.
249
+
245
250
  A native data format called GLDB is used to store and visualisations in a compact single file, using SQLite for storage and fast loading. A small number of other data formats are supported for import (OBJ surfaces, TIFF stacks etc).
246
251
  Further data import formats are supported with python scripts, with the numpy interface allowing rapid loading and manipulation of data.
247
252
 
248
- A CAVE2 virtual reality mode is provided by utilising Omegalib (http://github.com/uic-evl/omegalib) to allow use in Virtual Reality and Immersive Visualisation facilities, such as the CAVE2 at Monash, see (https://github.com/mivp/LavaVR).
249
- Side-by-side and quad buffer stereoscopic 3D support is also provided for other 3D displays.
250
-
251
253
  ### This repository ###
252
254
 
253
255
  This is the public source code repository for all development on the project.
@@ -258,13 +260,10 @@ Development happens in the "master" branch with stable releases tagged, so if yo
258
260
  It's now in the python package index, so you can install with *pip*:
259
261
 
260
262
  ```
261
- pip install --user lavavu
263
+ python -m pip install lavavu
262
264
  ```
263
265
 
264
- > If you don't have pip available, you can try `sudo easy_install pip` or just install [Anaconda](https://www.anaconda.com/download), which comes with pip and a whole lot of other useful packages for scientific work with python. Once in anaconda `pip install lavavu` will install the package.
265
-
266
- > Currently no binaries are provided and the installer needs to compile the library, so on Linux you may need some developer tools and headers first, eg: for Ubuntu:
267
- `sudo apt install build-essential libgl1-mesa-dev libx11-dev zlib1g-dev`
266
+ > Currently binary wheels are provided for Linux x86_64, MacOS x86_64 and ARM64 and Windows x86_64.
268
267
 
269
268
  To try it out:
270
269
 
@@ -281,7 +280,12 @@ Alternatively, clone this repository with *git* and build from source:
281
280
  ```
282
281
  git clone https://github.com/lavavu/LavaVu
283
282
  cd LavaVu
283
+ python -m pip install .
284
+ ```
285
+ or
286
+ ```
284
287
  make -j4
288
+
285
289
  ```
286
290
 
287
291
  If all goes well the viewer will be built, try running with:
@@ -290,14 +294,14 @@ If all goes well the viewer will be built, try running with:
290
294
  ### Dependencies ###
291
295
 
292
296
  * OpenGL and Zlib, present on most systems, headers may need to be installed
293
- * To use with python requires python 2.7+ and NumPy
294
- * For video output, requires: libavcodec, libavformat, libavutil, libswscale (from FFmpeg / libav)
297
+ * To use with python requires python 3.6+ and NumPy
298
+ * For video output, requires: PyAV or for built in encoding, libavcodec, libavformat, libavutil, libswscale (from FFmpeg / libav)
295
299
  * To build the python interface from source requires swig (http://www.swig.org/)
296
300
 
297
301
  ### Who do I talk to? ###
298
302
 
299
303
  * Report bugs/issues here on github: https://github.com/lavavu/LavaVu/issues
300
- * Contact developer: Owen Kaluza (at) monash.edu
304
+ * Contact developer: Owen Kaluza (at) anu.edu.au
301
305
 
302
306
  For further documentation / examples, see the online documentation
303
307
  * https://lavavu.github.io/Documentation
@@ -1,15 +1,15 @@
1
- lavavu/lavavu.py,sha256=5JdzlRaEjuWAPjBQrgIKEyIS2RRL2RIrQ2aWaHFkg2A,217172
1
+ lavavu/lavavu.py,sha256=I64ioPwTN1eyZcZEaF9RfdxncfLuihl-7VqqqJDFLCw,218528
2
2
  lavavu/vutils.py,sha256=6Vm_xl1bp9mWlfk7jgLDwbRw-tdE_oxin77YkLel3_4,5437
3
3
  lavavu/dict.json,sha256=lsZEHc7Bb6_lt7tkSBQMgkq7AEn_2hnhWzzdokmvIY8,52729
4
4
  lavavu/server.py,sha256=L-_yPCbNfwYxJCPjDQtr_lxPnDp4oMNVFyxXhBERYrQ,12468
5
5
  lavavu/points.py,sha256=jRRtA6CrcA46Y2jUJmkxnIiVoMC5a14xEd_ojhmjhz4,5871
6
6
  lavavu/LavaVuPython.py,sha256=ixhceiAr0gGGfCe3d2cY0IIgS4hbgoYJGa_RfgAmmrU,33207
7
- lavavu/tracers.py,sha256=0dB6v9Jg5G-IeZxiVFvbPoTycGvyAQBtgyEAZUmq4XU,4257
7
+ lavavu/tracers.py,sha256=-NwdBAj5vYWmLt4NE0gWl36SQShQhICR7vBE4rzNOFI,8488
8
8
  lavavu/convert.py,sha256=tbYRjLE2l1hI4d6tsW41Lia1JXmrWSc0-JAYCiMrjys,35516
9
- lavavu/control.py,sha256=C5fLZeeNazRODDlWyWhKINry86uW7jAu3x_MvmrKeO8,66434
9
+ lavavu/control.py,sha256=s32rtLPXXYtxpeXd6-iHdupmaMTJ3KhK6Vq-CLjf9OQ,66755
10
10
  lavavu/__init__.py,sha256=JroZQiUbuVN7ifixk2zNDGlyLGaM8bqfRbw4D_F9qIQ,191
11
11
  lavavu/amalgamate.py,sha256=Xloq1IZ4VUvYiROzQtwKcQIWC65c7EZrdiGVhZdolL8,586
12
- lavavu/_LavaVuPython.cpython-39-darwin.so,sha256=gwRBeDXL2uuGvq1iZjGOVtGykOQ8l0AVqIOjtjxP8Og,4125480
12
+ lavavu/_LavaVuPython.cpython-39-darwin.so,sha256=Guhc25OSKLKpewxlAyrD6ddVreyGrIV6B4kkH42Yo0o,4125480
13
13
  lavavu/aserver.py,sha256=SfFvLeDTcx1XtS8fY_HIrDmh3q9HicCBRSAriY5yyOE,12003
14
14
  lavavu/font.bin,sha256=fvi5zkvmq6gh9v3jXedBVuxNJWKmHtbjECzv6eT9wb4,225360
15
15
  lavavu/__main__.py,sha256=EbDetijCjyoiNxmExqnDGoRVs996tSVave5DML9zuMY,235
@@ -27,11 +27,11 @@ lavavu/html/dat.gui.min.js,sha256=S4_QjoXe4IOpU0f0Sj5jEQLTWPoX9uRl1ohB91jyhuw,56
27
27
  lavavu/html/OK-min.js,sha256=-4Gc1-dWfxP_w6r9ZOHa8u-X2BlGUoSQbX68lwCxQ3E,39115
28
28
  lavavu/html/emscripten.css,sha256=wkaIJhXaxuMchinQX9Z8c12cJomyvFQMeIZ62WGQEPQ,1813
29
29
  lavavu/html/drawbox.js,sha256=SJxFSmWd7QVFI5__66hFkKzLKeqg1JPcx-gJuqdMkXw,34590
30
- lavavu/html/webview.html,sha256=aFUGmVs3dT3R9X9IgA5WN8ND6tWFM0Ki5mgywqIRjz4,1521
30
+ lavavu/html/webview.html,sha256=8eilGjX8y_I5OjCvTm-ZAQDNOSCaT1yvogKFqK4lCS0,1521
31
31
  lavavu/html/emscripten-template.js,sha256=h63mzl3Lv7aQT1wMOiOucPOvHTJjpKkzsL-SFVYaZlA,6135
32
32
  lavavu/html/draw.js,sha256=57LlHlYRh0IvWVwzGDnF6PaCxYLzokpokLNCuZlG1ds,84108
33
33
  lavavu/html/dat-gui-light-theme.css,sha256=uPhvJs-1IAsdxudItyOw8lZy8Hrih0zmFM1u-xRwZ-M,1142
34
- lavavu/html/menu.js,sha256=WCli3UYcQ7frjNq0aAXNALu33iNiN8lpTYwKZ7pUcn4,21501
34
+ lavavu/html/menu.js,sha256=42h-BCnrmEpW4IdZRKC_FIXUVczJP0sm4ciZ2ccfs-k,21630
35
35
  lavavu/html/baseviewer.js,sha256=u3UhC1At6rMBKmcQ7d2DXTRxQ20wsQkc4lqA-7sL7i4,9567
36
36
  lavavu/osmesa/LavaVuPython.py,sha256=ixhceiAr0gGGfCe3d2cY0IIgS4hbgoYJGa_RfgAmmrU,33207
37
37
  lavavu/osmesa/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -47,9 +47,9 @@ lavavu/shaders/fontShader.vert,sha256=yCBmRugaPUeUQUdIh62-AK_5KELiJqVS2M82iyIqPw
47
47
  lavavu/shaders/pointShader.frag,sha256=3zREBdsimlL9fAXBPjhzomGaFUWmlG_QYkhwMTVURHQ,3291
48
48
  lavavu/shaders/volumeShader.vert,sha256=uGdQjGqi7V5kE6V7nxymfugtU4cbf6u570xBy13RgmY,78
49
49
  lavavu/shaders/default.frag,sha256=5XLYVfLwzN0sFT3aMYGmxbyquA_cmndp55YCOuy1t4E,180
50
- lavavu-1.9.0.dist-info/LICENSE.md,sha256=EhfNgC6BYh5gDEaq4tcrN3S0wbO4CtJO46botJnCF8c,8603
51
- lavavu-1.9.0.dist-info/RECORD,,
52
- lavavu-1.9.0.dist-info/WHEEL,sha256=0WiweP_1WiDsz1Eg0WaDZErWbFKfA6G6v7dDq6Kgtvg,108
53
- lavavu-1.9.0.dist-info/entry_points.txt,sha256=LC2qXR6EMe45Cb7zGAF99R9HFrAECP6Qkp_YuG6HZB0,44
54
- lavavu-1.9.0.dist-info/top_level.txt,sha256=JptS0k1nlBumjLs_0hITr3_XUJhxqvKBXD2jGho3E3A,7
55
- lavavu-1.9.0.dist-info/METADATA,sha256=yZSA74LHPQhIIUvqPUS3b9DhXpvQ3MLvl8lDLmENqS8,18267
50
+ lavavu-1.9.6.dist-info/RECORD,,
51
+ lavavu-1.9.6.dist-info/WHEEL,sha256=iCHKhVTaBUcivk1WLoz_ELkSkuV6v_Q95j2DC3RS4qw,135
52
+ lavavu-1.9.6.dist-info/entry_points.txt,sha256=LC2qXR6EMe45Cb7zGAF99R9HFrAECP6Qkp_YuG6HZB0,44
53
+ lavavu-1.9.6.dist-info/top_level.txt,sha256=JptS0k1nlBumjLs_0hITr3_XUJhxqvKBXD2jGho3E3A,7
54
+ lavavu-1.9.6.dist-info/METADATA,sha256=EHgjIlNax60denK4kKXeAJziT1zFhX8e-9OqQUVd9WE,17877
55
+ lavavu-1.9.6.dist-info/licenses/LICENSE.md,sha256=EhfNgC6BYh5gDEaq4tcrN3S0wbO4CtJO46botJnCF8c,8603
@@ -1,5 +1,6 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp39-cp39-macosx_10_9_x86_64
5
+ Generator: delocate 0.13.0
5
6