Time dependent data in Mayavi - python

Assuming I have a 4d numpy array like this: my_array[x,y,z,t].
Is there a simple way to load the whole array into Mayavi, and simply selecting the t I want to investigate for?
I know that it is possible to animate the data, but I would like to rotate my figure "on the go".

It is possible to set up a dialogue with a input box in which you can set t.
You have to use the traits.api, it could look like this:
from traits.api import HasTraits, Int, Instance, on_trait_change
from traitsui.api import View, Item, Group
from mayavi.core.ui.api import SceneEditor, MlabSceneModel, MayaviScene
class Data_plot(HasTraits):
a = my_array
t = Int(0)
scene = Instance(MlabSceneModel, ())
plot = Instance(PipelineBase)
#on_trait_change('scene.activated')
def show_plot(self):
self.plot = something(self.a[self.t]) #something can be surf or mesh or other
#on_trait_change('t')
def update_plot(self):
self.plot.parent.parent.scalar_data = self.a[self.t] #Change the data
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene),
show_label=False),
Group('_', 't'),
resizable=True,
)
my_plot = Data_plot(a=my_array)
my_plot.configure_traits()
You can also set up a slider with the command Range instead of Int if you prefer this.

Related

Jupyter/Plotly - how to replace or update plot from add_trace?

Consider this Jupyter Python code, which uses Plotly:
import plotly.graph_objs as go
import numpy as np
from ipywidgets import widgets
from IPython.display import display
import random
mybutton = widgets.Button(description="Redraw")
xs = np.linspace(start=0, stop=10, num=100)
fig = go.FigureWidget( layout=go.Layout() )
# NB: function needs to be written in a way, that returns np.array for input np.array!
# or - use np.vectorize, to apply it element-by-element
def TestFunc(inval):
return inval+2*random.random()
fig.add_trace(go.Scatter(x=xs, y=np.vectorize(TestFunc)(xs),
mode='lines',
name='Test'))
def on_button_clicked(b):
fig.add_trace(go.Scatter(x=xs, y=np.vectorize(TestFunc)(xs),
mode='lines',
name='Test'))
mybutton.on_click(on_button_clicked)
widgets.VBox([mybutton, fig])
What I want to do, is redraw the function anew, whenever I click the button. However, since I use add_trace in the button callback, I get new traces added - I don't get the original one replaced:
So, my question is:
How do I obtain a reference to a "trace", added with add_trace, so that I could replace it? (say, fig.traces[0] = ...)
What is the best way to redraw the figure with a new retrace, with the minimal amount of object instantiation (I guess, I could do fig = go.FigureWidget( ... ) ... upon each button click, but that would have to recreate everything; I'd think, just recreating the y array, and triggering a redraw would be more "optimized")
OK, found something - still not sure if this is the way to do it, so if someone knows better, please post...
But anyways, fig.add_trace returns a reference that you can store in a variable; eventually that variable also contains the .x and .y arrays, and the .y array can be directly replaced, like so:
import plotly.graph_objs as go
import numpy as np
from ipywidgets import widgets
from IPython.display import display
import random
mybutton = widgets.Button(description="Redraw")
xs = np.linspace(start=0, stop=10, num=100)
fig = go.FigureWidget( layout=go.Layout() )
# NB: function needs to be written in a way, that returns np.array for input np.array!
# or - use np.vectorize, to apply it element-by-element
def TestFunc(inval):
return inval+2*random.random()
mytrace = fig.add_trace(go.Scatter(x=xs, y=np.vectorize(TestFunc)(xs),
mode='lines',
name='Test'))
print(repr(mytrace))
def on_button_clicked(b):
mytrace.data[0].y = np.vectorize(TestFunc)(xs)
mybutton.on_click(on_button_clicked)
widgets.VBox([mybutton, fig])
The above code works as intended - but I'm not yet sure whether it's the most optimized way to do it...

View vs. Viewable with displaying widget

I am putting together an interactive dashboard using the pyviz ecosystem. One feature of the dashboard is that the underlying data may change based on a widget selector. Below is an example code showing the issue I have with getting the time widget slider to appear:
Package Versions:
panel: 0.5.1
param: 1.9.0
holoviews: 1.12.3
geoviews: 1.6.2
Example:
import xarray as xr
import panel as pn
import numpy as np
import param as pm
import holoviews as hv
import geoviews as gv
from matplotlib import cm
import geoviews.tile_sources as gts
from holoviews.operation.datashader import rasterize
from collections import OrderedDict as odict
from holoviews import opts
renderer = hv.renderer('bokeh')
pn.extension()
dset = xr.DataArray(np.random.random((100,100,100)),coords={'X':np.arange(100),'Y':np.arange(100),'T':np.arange(100)},dims=['X','Y','T']).to_dataset(name='test')
dset = gv.Dataset(dset, ['X', 'Y', 'T'], 'test').to(gv.QuadMesh, groupby='T').opts(cmap='viridis', colorbar=True, show_frame=False)
fields = odict([('test','test')])#odict([(v.get('label',k),k) for k,v in source.metadata['fields'].items()])
aggfns = odict([(f.capitalize(),f) for f in ['mean','std','min','max','Pixel Level']])#'count','sum','min','max','mean','var','std']])#,'None (Pixel Level)']])
cmaps = odict([(n,cm.get_cmap(n)) for n in ['viridis','seismic','cool','PiYG']])
maps = ['EsriImagery','EsriNatGeo', 'EsriTerrain', 'OSM']
bases = odict([(name, gts.tile_sources[name].relabel(name)) for name in maps])
gopts = hv.opts.WMTS(responsive=True, xaxis=None, yaxis=None, bgcolor='black', show_grid=False)
class Explorer_Test(pm.Parameterized):
field = pm.Selector(fields)
cmap = pm.Selector(cmaps)
basemap = pm.Selector(bases)
data_opacity = pm.Magnitude(1.00)
map_opacity = pm.Magnitude(1.00)
agg_fn_ = pm.Selector(aggfns,label='Aggregation**',default='mean')
#pm.depends('field', 'agg_fn_')
def aggregator(self):
field = None if self.field == "counts" else self.field
return self.agg_fn(field)
#pm.depends('map_opacity', 'basemap')
def tiles(self):
return self.basemap.opts(gopts).opts(alpha=self.map_opacity)
def viewable(self,**kwargs):
rasterized = rasterize(dset, precompute=True).opts(colorbar=True, height=800, show_frame=False).apply.opts(cmap=self.param.cmap,alpha=self.param.data_opacity)
return hv.DynamicMap(self.tiles)*rasterized
explorer_test = Explorer_Test(name="")
When I display the plot like:
panel = pn.Row(pn.Param(explorer_test.param, expand_button=False),explorer_test.viewable())
panel.servable()
The time widget appears:
Whereas:
panel = pn.Row(pn.Param(explorer_test.param, expand_button=False),explorer_test.viewable)
panel.servable()
In the first example, if I select an alternative dataset (based on a param.Selector widget - not shown in this example) it does not redraw the image. However, in the 2nd example, the image is redrawn, but I am missing the time slider.
UPDATE - Solution
Here is the workaround as per James' solutions (thanks!). This example includes changing the dataset and the variable (within each dataset) and the time parameter.
import xarray as xr
import panel as pn
import numpy as np
import param as pm
import holoviews as hv
import geoviews as gv
from holoviews.operation.datashader import rasterize
from collections import OrderedDict as odict
renderer = hv.renderer('bokeh')
pn.extension()
#Define Example Datasets
dset1 = xr.merge([xr.DataArray(np.random.random((50,50,50)),coords={'X':np.arange(50),'Y':np.arange(50),'T':np.arange(50)},dims=['X','Y','T']).to_dataset(name='var1'),
xr.DataArray(np.random.random((50,50,10))*.1,coords={'X':np.arange(50),'Y':np.arange(50),'T':np.arange(10)},dims=['X','Y','T']).to_dataset(name='var2')])
dset2 = xr.DataArray(np.random.random((50,50,20))*10,coords={'X':np.arange(50)/2.,'Y':np.arange(50)/3.,'T':np.arange(20)},dims=['X','Y','T']).to_dataset(name='var1')
data_dict = {'dset1':dset1,'dset2':dset2}
#Plot Datasets
class sel_dset_var():
def dset1_var1():
return rasterize(gv.Dataset(dset1.var1, ['X', 'Y', 'T'], 'test1').to(gv.QuadMesh, groupby='T')()).opts(cmap='viridis',colorbar=True, height=200, show_frame=False)
def dset1_var2():
return rasterize(gv.Dataset(dset1.var2, ['X', 'Y', 'T'], 'test1').to(gv.QuadMesh, groupby='T')()).opts(cmap='viridis',colorbar=True, height=200, show_frame=False)
def dset2_var1():
return rasterize(gv.Dataset(dset2.var1, ['X', 'Y', 'T'], 'test1').to(gv.QuadMesh, groupby='T')()).opts(cmap='viridis',colorbar=True, height=200, show_frame=False)
#Dashboard
class Explorer_Test(pm.Parameterized):
dset = pm.Selector(odict([('Dataset1','dset1'),('Dataset2','dset2')]),default='dset1')
varss = pm.Selector(list(dset1.data_vars),default=list(dset1.data_vars)[0])
time1 = pm.Selector(dset1.var1.coords['T'].values,default=dset1.var1.coords['T'].values[0])
#pm.depends('dset',watch=True)
def update_var(self):
self.param['varss'].objects = list(data_dict[self.dset].data_vars)
self.param.set_param(varss=list(data_dict[self.dset].data_vars)[0])
#pm.depends('dset',watch=True)
def update_var(self):
self.param['varss'].objects = list(data_dict[self.dset].data_vars)
self.param.set_param(varss=list(data_dict[self.dset].data_vars)[0])
def elem(self):
return getattr(sel_dset_var,self.dset+'_'+self.varss)()
#pm.depends('varss','dset',watch=True)
def update_time(self):
self.param['time1'].objects =data_dict[self.dset][self.varss].dropna(dim='T').coords['T'].values
self.param.set_param(time1=data_dict[self.dset][self.varss].dropna(dim='T').coords['T'].values[0])
def elem_yr(self):
return getattr(self.elem(),'select')(T=self.time1)
def viewable(self,**kwargs):
return self.elem_yr
explorer_test = Explorer_Test(name="")
panel = pn.Row(pn.Param(explorer_test.param, expand_button=False),explorer_test.viewable())
panel.servable()
Cheers!
This code looks like it's derived from my http://datashader.org/dashboard.html example. In my example, the output from the viewable() method is already fully dynamic, and does not ever need to be regenerated, being already linked internally to all the widgets and controls that affect how it appears. Whereas if you pass viewable as a method name to Panel (rather than result of calling that method), you're asking Panel to call viewable() for you whenever it determines that the result from an initial call becomes stale. This simple re-run-the-method approach is appropriate for very simple cases of all-or-nothing computation, but not really useful here when the objects are already dynamic themselves and where specific controls are tied to specific aspects of the plot. (Why you also don't get a time widget in that case I'm not sure; it's not a recommended usage, but I would have thought it should still work in giving you a widget.)
Anyway, I don't think you should be trying to get the second case above to work, only the first one. And there the problem isn't the lack of the slider, it sounds like it's that you're trying to get the plot to be responsive to changes in your data source. Luckily, that case is already illustrated in the example in http://datashader.org/dashboard.html ; there rasterize dynamically wraps a method that returns the appropriate column of the data to show. You should be able to adapt that approach to make it dynamically reflect the state of some other widget that lets the user select the dataset.

(pyqtgraph) How do I show actual (non-scientific) tick values on a logarithmic Y axis?

My first StackOverflow question, apologies for poor format or if this has been asked before (I can't find the answer anywhere). I'm using Python 3 and pyqtgraph.
I want to graph (price) data on a logarithmic y scale, but I want to retain the actual values, not the scientific notation.
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui
pg.mkQApp()
pw = pg.PlotWidget()
pw.show()
myplot = pg.PlotDataItem([1,2,4,8,16,32,64,128])
p1 = pw.plotItem
p1.hideAxis('left')
p1.showAxis('right')
p1.showGrid(y = True, alpha = 1.0)
p1.addItem(myplot)
p1.setLogMode(y=True)
QtGui.QApplication.instance().exec_()
myplot is a straight line as intended in logMode, but tick values are scientific:
I modified a subclass from a related question (PyQtgraph y axis label non scientific notation) and tried to override the AxisItem, but it doesn't seem to 'activate' via myplot. :
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui
class NonScientific(pg.AxisItem):
def __init__(self, *args, **kwargs):
super(NonScientific, self).__init__(*args, **kwargs)
def tickStrings(self, values, scale, spacing):
return [int(value*1) for value in values] #This line return the NonScientific notation value
def logTickStrings(self, values, scale, spacing):
return [int(value*1) for value in values] #This line return the NonScientific notation value
pg.mkQApp()
pw = pg.PlotWidget()
pw.show()
myplot = pg.PlotDataItem([1,2,4,8,16,32,64,128], axisItems={'right': NonScientific(orientation='right')})
p1 = pw.plotItem
p1.hideAxis('left')
p1.showAxis('right')
p1.showGrid(y = True, alpha = 1.0)
p1.addItem(myplot)
p1.setLogMode(y=True)
QtGui.QApplication.instance().exec_()
My questions are:
Am I overriding a subclass correctly? Is AxisItem even the right class to override? If so, is there a way to 'get' the AxisItem of the p1 plotItem? I tried p1.getAxis('right') but don't know how to override the class that way
Is there a way to retain the actual values along the y axis in a log scale, rather than simply modifying strings, without transforming to log numbers?
What I mean by question 2 is, I have tried a workaround that is probably the wrong way to do it. I added these lines just before the end:
yaxticks = [[(1, '1.00'), (2, '2.00'), (4, '4.00'), (8, '8.00'), (16, '16.00'), (32, '32.00'), (64, '64.00'), (128, '128.00')]]
yax = p1.getAxis('right')
yax.setTicks(yaxticks)
As well as tried this:
yaxticks = [[(0.1, '1.00'), (0.2, '2.00'), (0.4, '4.00'), (0.8, '8.00'), (1.6, '16.00'), (3.2, '32.00'), (6.4, '64.00'), (12.8, '128.00')]]
Results were as follows, compared with original:
Based on where those lines are falling, I can tell I could get this to work by referencing the log values at each point, but this seems unpythonic and a band-aid solution. My application works intimately with price data and I worry all graphical functions that reference price data would need to transform to and from log values.
Is there a better way?

Updating data of an Image Plane Widget

I am working on a visualization of different vector fields.
For this purpose I am using the Mayavi Library in Python 2.7 (I think), to create a Image Plane Widget (IPW) and a slider to change the data of the vector field while the visualization is open, but my IPW won't change.
It works if I render the IPW new each time the slider is changed, but that's not what I want.
Is there a way to change the data of an IPW while the program is running without rendering a new Plane each time?
I have written following code:
import numpy as np
from mayavi import mlab
from matplotlib.scale import scale_factory
from traits.api import HasTraits, Range, Instance, Array, \
on_trait_change
from traitsui.api import View, Item, Group
from mayavi.core.pipeline_base import PipelineBase
from mayavi.core.ui.api import MayaviScene, SceneEditor, \
MlabSceneModel
class Modell(HasTraits):
p = Array
n = Range(0, 9, 5)
#p is a 4 dimensional Array p[10][20][20][20]
scene = Instance(MlabSceneModel, ())
plot = Instance(PipelineBase)
#on_trait_change('n,scene.activated')
def update_plot(self):
self.src = mlab.pipeline.scalar_field(self.p[self.n])
if self.plot is None:
self.plot = self.scene.mlab.pipeline.image_plane_widget(self.src,
plane_orientation='z_axes',
slice_index=10,
vmin=0, vmax=120)
else:
'''here should be the update function, i tried using mlab_source.set(self.src=self.src)
and all variations of expressions in the brackets but nothing worked.
I also searched for functions in IPW itself but didn't find something that could help me.'''
#The layout of the dialog created
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene),
height=400, width=400, show_label=False),
Group('_', 'n'),
resizable=True,
)
my_model = Modell(p=p)
my_model.configure_traits()
I tried updating the pipeline and updating the data with self.plot.update_pipeline() and self.plot.update_data() but this doesn't work either.
Ok I found the solution for my problem, the trick is to change the data directly through the pipeline. So in my code I just have to set the following command into the else segment:
self.plot.parent.parent.scalar_data = self.p[self.n]

Updating Points of TVTK Dataset in MayaVI (without flushing pipeline)

I want to use MayaVI for visualization of large simulation data, saved as a VTKUnstructuredGrid (or here TVTK Unstructured Grid). After loading the Grid, I want to quickly update the grid points using numpy arrays, without changing anything else in the model.
So far I update the points and then call the modified()-method, which flushes the complete pipeline and thus slows down the visualization a lot. My question is now: Is there any chance to update the points in a VTKDataset without reloading the whole pipeline?
I am doing the visualization using Traits; simplified my code looks like:
import numpy as np
from enthought.traits.api import HasTraits, Range, Instance, on_trait_change
from enthought.traits.ui.api import View, Item, HGroup, HSplit, VSplit
from enthought.tvtk.pyface.scene_editor import SceneEditor
from enthought.mayavi.tools.mlab_scene_model import MlabSceneModel
from enthought.mayavi.core.ui.mayavi_scene import MayaviScene
from enthought.mayavi import mlab
from enthought.tvtk.api import tvtk
from enthought.mayavi.modules.surface import Surface
from enthought.tvtk.pyface.scene_editor import SceneEditor
class Visu(HasTraits):
timestep = Range(50,100,50)
pts = tvtk.Points()
ugrid = tvtk.UnstructuredGrid()
scene = Instance(MlabSceneModel, ())
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene), height=250, width=300, show_label=True),HGroup('_', 'timestep'), resizable=True )
#on_trait_change('scene.activated')
def ini(self):
filename = 'original3dplot'
reader = tvtk.LSDynaReader(file_name = filename)
reader.update()
self.ugrid = reader.get_output()
self.surface = self.scene.mlab.pipeline.surface(self.ugrid)
#on_trait_change('timestep')
def update_visu(self):
update_coord = np.loadtxt('newcoordinates'+str(self.timestep))
self.pts.from_array(update_coord)
self.ugrid.points = self.pts
self.ugrid.modified()
visualization = Visu()
visualization.configure_traits()

Categories