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

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()

Related

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.

How to Make Scipy Dendrogram Read Japanese Words/Terms

I am trying to conduct hierarchical clustering through Japanese words/terms and using scipy.cluster.hierarchy.dendrogram to plot the results. However, the plot cannot show the Japanese words/terms but instead use small rectangles. At first, I was thinking this may be because when I create the dictionary, the keys are unicode not Japanese (as the question I asked here). Then I was suggested to use Python3 to solve such issue and I finally make the dictionary key in Japanese words instead of unicode (as the question I ask here). However, it turns out that even if I feed the label parameter of scipy.cluster.hierarchy.dendrogram with Japanese words/terms, the plot still cannot show those words. I have checked several similar posts but it seems like there is still no clear solution. My codes are as follows:
import pandas as pd
import numpy as np
from sklearn import decomposition
from sklearn.cluster import AgglomerativeClustering as hicluster
from scipy.spatial.distance import cdist, pdist
from scipy import sparse as sp ## Sparse Matrix
from scipy.cluster.hierarchy import dendrogram
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.style.use('ggplot')
## Import Data
allWrdMat10 = pd.read_csv("../../data/allWrdMat10.csv.gz",
encoding='CP932')
## Set X as CSR Sparse Matrix
X = np.array(allWrdMat10)
X = sp.csr_matrix(X)
def plot_dendrogram(model, **kwargs):
# Children of hierarchical clustering
children = model.children_
# Distances between each pair of children
# Since we don't have this information, we can use a uniform one
for plotting
distance = np.arange(children.shape[0])
# The number of observations contained in each cluster level
no_of_observations = np.arange(2, children.shape[0]+2)
# Create linkage matrix and then plot the dendrogram
linkage_matrix = np.column_stack([children, distance,
no_of_observations]).astype(float)
# Plot the corresponding dendrogram
dendrogram(linkage_matrix, **kwargs)
dict_index = {t:i for i,t in enumerate(allWrdMat10.columns)}
dictlist = []
temp = []
akey = []
avalue = []
for key, value in dict_index.items():
akey.append(key)
avalue.append(value)
temp = [key,value]
dictlist.append(temp)
avalue = np.array(avalue)
X_transform = X[:, avalue < 1000].transpose().toarray()
freq1000terms = akey
freq1000terms = np.array(freq1000terms)[avalue < 1000]
hicl_ward = hicluster(n_clusters=40,linkage='ward', compute_full_tree =
False)
hiclwres = hicl_ward.fit(X_transform)
plt.rcParams["figure.figsize"] = (15,6)
model1 = hiclwres
plt.title('Hierarchical Clustering Dendrogram (Ward Linkage)')
plot_dendrogram(model1, p = 40, truncate_mode = 'lastp', orientation =
'top', labels=freq1000terms[model1.labels_], color_threshold = 991)
plt.ylim(959,1000)
plt.show()
You need to give matplotlib a valid font to display Japanese characters with. You can find the available fonts from your system by using the following code:
import matplotlib.font_manager
matplotlib.font_manager.findSystemFonts(fontpaths=None)
It will give you a list of system fonts that matplotlib can use:
['c:\\windows\\fonts\\seguisli.ttf',
'C:\\WINDOWS\\Fonts\\BOD_R.TTF',
'C:\\WINDOWS\\Fonts\\GILC____.TTF',
'c:\\windows\\fonts\\segoewp-light.ttf',
'c:\\windows\\fonts\\glsnecb.ttf',
...
...
'c:\\windows\\fonts\\elephnti.ttf',
'C:\\WINDOWS\\Fonts\\COPRGTB.TTF']
Pick a font that supports Japanese character encoding, and give it as a parameter to matplotlib at the beginning of your code as following:
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Yu Gothic" # I.E Yu Gothic, supports shift-jis
This is a global parameter setting, other plots on the same project will also use the same font family. If you want to change it for a single text, you can use font properties of matplotlib text object.
Also: If you can't find/see an appropriate font you can download a font like code2000, install it and use it the same way. (For the font to show up at the list, you may need to clear matplotlib's cache)

Save contour images generated in a loop as a single pdf file (2 images per page preferably)

I have written this code which will generate a number of contour plots, each of which corresponds to a single text file. I have multiple text files. Currently, I am able to generate all of the images separately in png format without any issues.
When I try to save the images as a pdf file, it is saving only the last image generated in a loop.I tried using the PdfPages package. This question is similar to the one that I posted before but with a different question. Similar
Issue: I want to able to generate all of the images into a single pdf file automatically from python. So for eg. if I have 100 text files, then I want to save all of the 100 images onto a single pdf file.Also ideally I want to save 2 images in a single page in the pdf file. There are some questions in SO about this, but I couldn't find an appropriate solution for my issue. Since I have many case for which I have to generate the images, I want to save them as a single pdf file as it is more easier to analyze them. I would appreciate any suggestions/advice to help me with this.
This is link for the sample text file Sample Text
ges
from __future__ import print_function
import numpy as np
from matplotlib import pyplot as plt
from scipy.interpolate import griddata
from matplotlib.backends.backend_pdf import PdfPages
path = 'location of the text files'
FT_init = 5.4311
delt = 0.15
TS_init = 140
dj_length = 2.4384
def streamfunction2d(y,x,Si_f,q):
with PdfPages('location of the generated pdf') as pdf:
Stf= plt.contour(x,y,Si_f,20)
Stf1 = plt.colorbar(Stf)
plt.clabel(Stf,fmt='%.0f',inline=True)
plt.figtext(0.37,0.02,'Flowtime(s)',style= 'normal',alpha=1.0)
plt.figtext(0.5,0.02,str(q[p]),style= 'normal',alpha=1.0)
plt.title('Streamfunction_test1')
plt.hold(True)
plt.tight_layout()
pdf.savefig()
path1 = 'location where the image is saved'
image = path1+'test_'+'Stream1_'+str((timestep[p]))+'.png'
plt.savefig(image)
plt.close()
timestep = np.linspace(500,600,2)
flowtime = np.zeros(len(timestep))
timestep = np.array(np.round(timestep),dtype = 'int')
###############################################################################
for p in range(len(timestep)):
if timestep[p]<TS_init:
flowtime[p] = 1.1111e-01
else:
flowtime[p] = (timestep[p]-TS_init)*delt+FT_init
q = np.array(flowtime)
timestepstring=str(timestep[p]).zfill(4)
fname = path+"ddn150AE-"+timestepstring+".txt"
f = open(fname,'r')
data = np.loadtxt(f,skiprows=1)
data = data[data[:, 1].argsort()]
data = data[np.logical_not(data[:,11]== 0)]
Y = data[:,2] # Assigning Y to column 2 from the text file
limit = np.nonzero(Y==dj_length)[0][0]
Y = Y[limit:]
Vf = data[:,11]
Vf = Vf[limit:]
Tr = data[:,9]
Tr = Tr[limit:]
X = data[:,1]
X = X[limit:]
Y = data[:,2]
Y = Y[limit:]
U = data[:,3]
U = U[limit:]
V = data[:,4]
V = V[limit:]
St = data[:,5]
St = St[limit:]
###########################################################################
## Using griddata for interpolation from Unstructured to Structured data
# resample onto a 300x300 grid
nx, ny = 300,300
# (N, 2) arrays of input x,y coords and dependent values
pts = np.vstack((X,Y )).T
vals = np.vstack((Tr))
vals1 = np.vstack((St))
# The new x and y coordinates for the grid
x = np.linspace(X.min(), X.max(), nx)
y = np.linspace(Y.min(), Y.max(), ny)
r = np.meshgrid(y,x)[::-1]
# An (nx * ny, 2) array of x,y coordinates to interpolate at
ipts = np.vstack(a.ravel() for a in r).T
Si = griddata(pts, vals1, ipts, method='linear')
print(Ti.shape,"Ti_Shape")
Si_f = np.reshape(Si,(len(y),len(x)))
print(Si_f.shape,"Streamfunction Shape")
Si_f = np.transpose(Si_f)
streamfunction2d(y,x,Si_f,q)
Edit : As you mentioned matplotlib is probably able to handle everything by itself using PdfPages function. See this related answer. My original answer is a hack.
I think the error in your code is that you are creating another PdfPage object each time you go through the loop. My advice would be to add the PdfPage object as an argument to your streamfunction2d function and create the PdfPage object once and for all before the loop (using a with statement as in the documentation seems a good idea).
Example:
def streamfunction2d(y,x,Si_f,q,pdf):
# (...)
pdf.savefig(plt.gcf())
with PdfPages('output.pdf') as pdf:
for p in range(len(timestep)):
# (...)
streamfunction2d(y,x,Si_f,q,pdf)
Original answer:
Here is a quick and dirty solution using the pdfunite software.
from matplotlib import pyplot as plt
import numpy as np
import subprocess
import os
X = np.linspace(0,1,100)
for i in range(10):
# random plot
plt.plot(X,np.cos(i*X))
# Save each figure as a pdf file.
plt.savefig("page_{:0}.pdf".format(i))
plt.clf()
# Calling pdfunite to merge all the pages
subprocess.call("pdfunite page_*.pdf united.pdf",shell=True)
# Removing temporary files
for i in range(10):
os.remove("page_{:0}.pdf".format(i))
It uses two things:
You can save your figures as pdf using matplotlib's savefig command.
You can call other programs using the subprocess library. I used pdfunite to merge all the pages. Be sure it is available on your machine !
If you want to have several graph by page, you can use subplots.
Alternatively, you could use another python library (such as pyPDF) to merge the pages, but it would require slightly more code. Here is an (untested) example:
from matplotlib import pyplot as plt
import numpy as np
from pyPdf import PdfFileWriter, PdfFileReader
# create an empty pdf file
output = PdfFileWriter()
X = np.linspace(0,1,100)
for i in range(10):
# random plot
plt.plot(X,np.cos(i*X))
# Save each figure as a pdf file.
fi = "page_{:0}.pdf".format(i)
plt.savefig(fi)
plt.clf()
# add it to the end of the output
input = PdfFileReader(file(fi, "rb"))
output.addPage(input.getPage(0))
# Save the resulting pdf file.
outputStream = file("document-output.pdf", "wb")
output.write(outputStream)

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]

Time dependent data in Mayavi

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.

Categories