Python Using pyplot slider with subplots - python

I am quite new to Python, so please excuse if this is a stupid beginner's error. However I am struggling with it for quite some time.
I want to create a figure with n x m subplots, each subplot being np.array of shape [1024,264,264]. As I am looking for differences occuring in the stack along the 0-dimension I want to use a slider to explore all stacks in my figure simultaneously.
The slider instance works nicely for a figure with one subplot but I can't bring them all to work.
That's the code I am using:
import os
from matplotlib import pyplot as plt
import numpy as np
import glob
import h5py
#Define the xy size of the mapped array
xsize=3
ysize=3
lengthh5=9
readlist=[]
for i in range (0,lengthh5):
npraw=np.random.rand(200,50,50)
readlist.append (npraw)
''' Slider visualization'''
from matplotlib.widgets import Slider
fig=plt.figure()
for k in range (0,lengthh5):
ax=fig.add_subplot(xsize,ysize,k)
frame = 10
l = ax.imshow(readlist[k][frame,:,:])
plt.axis('off')
sframe = Slider(fig.add_subplot(50,1,50), 'Frame', 0, len(readlist[0])-1, valinit=0)
def update(val):
frame = np.around(sframe.val)
l.set_data(readlist[k][frame,:,:])
sframe.on_changed(update)
plt.show()
For this particular case I stripped it down to a 3x3 array for my figure and just create randmom (smaller) arrays.
The slider is interestinly only operable on the second last subplot. However I have no real idea how to link it to all subplots simulatenously. Perhaps someone has an idea how to do this.
Thanks a lot in advance,
Tilman

You need to store each imshow AxesImage in a list and inside update, loop over all of them and update each based on the slider,
import os
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
import numpy as np
import glob
import h5py
#Define the xy size of the mapped array
xsize=3
ysize=3
lengthh5=9
readlist=[]
for i in range (0,lengthh5):
npraw=np.random.rand(200,50,50)
readlist.append (npraw)
fig=plt.figure()
ls = []
for k in range (0,lengthh5):
ax=fig.add_subplot(xsize,ysize,k)
frame = 10
l = ax.imshow(readlist[k][frame,:,:])
ls.append(l)
plt.axis('off')
sframe = Slider(fig.add_subplot(50,1,50), 'Frame',
0, len(readlist[0])-1, valinit=0)
def update(val):
frame = np.around(sframe.val)
for k, l in enumerate(ls):
l.set_data(readlist[k][frame,:,:])
sframe.on_changed(update)
plt.show()

Related

Not sure why my Pyplot subplot wont update over time

I am working on a project that requires me to log data over time, while also plotting the data on screen with a live line graph. I have gotten everything but the line graph to work this far and am unsure what I am doing incorrectly. This is the imports that I am currently using.
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import pyplot as plt
from matplotlib import style
from tkinter import *
from PIL import Image
import numpy as np
import serial
from serial import Serial
import sqlite3
import time
from datetime import datetime
from array import *
import cv2
from pathlib import Path
from itertools import count
The data that is meant to be used for the Y axis plotting is stored in an array of data. Each index in this array is to hold the last read value from the sensors, i=0 is sensor 1 and so on.
A=[0,0,0,0,0,0,0,0]
This is the definition of the subplot that I am trying to draw to. I think I am setting this up correctly, however I am not getting the expected result so likely not.
fig1 = plt.Figure(dpi=100, facecolor="#f0f0f0")
a = fig1.add_subplot(111)
a.patch.set_facecolor("#f0f0f0")
a.set_xlabel('time (Sec)')
a.set_ylabel('pressure(kPa)')
a.set_ylim(0,100)
a.set_xlim(0,30)
graph1 = FigureCanvasTkAgg(fig1, master=root)
graph1.get_tk_widget().place(x=10, y=220, width=210, height=280)
graph1.draw();
I am currently just trying to get one of the lines to draw first before handling the, seemingly, minor issue that is overlapping multiple lines. This is the function that I am trying to use in order to draw said line.
def graph_plotdata():
global A
global a
line1 = []
time = []
time.append(next(index))
line1.append(A[0])
a.cla()
a.plot(time, line1)
graph1.draw()
I have tried several iterations of this code in order attempt to solve this problem. The closest I have to getting it to work is in the current state in which something is happening however instead of keeping my min and max limits on the graph it completely reformats my plot and plots an "invisible" line.
Before starting:
After starting:
I am not overwhelmingly experienced when is comes to python libraries so bare with me.
I use a dictionary to store the various lines and line plots and then update the plots using set_data(xdata, ydata). I'm not sure how your datastream works, so mine just updates when I push the update button and generates a random reading. You'll obviously want to change those parts to match your data input.
fig, ax = plt.subplots(1, 1)
plt.subplots_adjust(bottom = 0.20)
num_sensors = 10
latest_reading = [0]*num_sensors
lines = {index: [0] for index in range(num_sensors)}
times = [0]
line_plots = {index: ax.plot(lines[index])[0] for index in range(num_sensors)}
btn_ax = plt.axes([0.475, 0.05, 0.10, 0.05])
def update(event):
latest_reading = np.random.randint(0, 10, num_sensors)
times.append(times[-1] + 1)
for index in range(num_sensors):
lines[index].append(latest_reading[index])
line_plots[index].set_data(times, lines[index])
# Adjust limits
max_time_window = 20
ax.set_xlim(max(0, max(times)-max_time_window), max(times))
ax.set_ylim(0, max(lines))
plt.draw()
btn = mpl.widgets.Button(btn_ax, 'Update')
btn.on_clicked(update)
Thank you for the response.
I figured out the issue, it had nothing to do with my matplotlib/tkinter implementation. I just totally missed that I had a scope inheritance issue. The lists of 'time' and 'line1' are not persistent in the entire scope and therefore being rewritten to empty lists every time the 'graph_plotdata()' function is called.
my solution is as follows:
timet = []
line1 = []
"""----------Graph Updater-----------"""
def graph_plotdata():
global B
global a
global graph1
global timet
global line1
timet.append(next(index))
line1.append(B[0])
a.clear()
a.plot(timet, line1)
a.patch.set_facecolor("#f0f0f0")
a.set_xlabel('time (Sec)')
a.set_ylabel('pressure(kPa)')
a.set_ylim(0,30)
a.set_xlim(0,30)
graph1.draw()
Hopefully this helps people in the future running into a similar issue!

Displaying Colormap/legend with x,y,z plot and fourth variable

I'm using Pandas and am very new to programming. I'm plotting Energy Deposited (eDep) as a function of its x,y and z positions. So far, was successful in getting it to plot, but it won't let me plot the colormap beside my scatter plot! Any help is much appreciated
%matplotlib inline
import pandas as pd
import numpy as np
IncubatorBelow = "./Analysis.Test.csv"
df = pd.read_csv(IncubatorBelow, sep = ',', names['Name','TrackID','ParentID','xPos','yPos','zPos','eDep','DeltaE','Einit','EventID'],low_memory=False,error_bad_lines=False)
df["xPos"] = df["xPos"].str.replace("(","")
df["zPos"] = df["zPos"].str.replace(")","")
df.sort_values(by='Name', ascending=[False])
df.dropna(how='any',axis=0,subset=['Name','TrackID','ParentID','xPos','yPos','zPos','eDep','DeltaE','Einit','EventID'], inplace=True)
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
df['xPos'] = df['xPos'].astype(float)
df['yPos'] = df['yPos'].astype(float)
df['zPos'] = df['zPos'].astype(float)
#df10[df10['Name'].str.contains("e-")]
threedee = plt.figure().gca(projection='3d')
threedee.scatter(df["xPos"], df["yPos"], df["zPos"], c=df["eDep"], cmap=plt.cm.coolwarm)
threedee.set_xlabel("x(mm)")
threedee.set_ylabel("y(mm)")
threedee.set_zlabel("z(mm)")
plt.show()
Heres what the plot looks like!
Its from a particle physics simulation using GEANT4. The actual files are extremely large (3.7GB's that I've chunked into 40ish MB's) and this plot only represents a small fraction of the data.

Scrollable Bar graph matplotlib

I am a newbie to python and am trying to plot a graph for some frame ids, the frame ids can vary from just about 10 in number to 600 or above in number.
Currently, I have this and it works and displays 37 ids together but if I have suppose 500 ids, it clutters them and overlaps the text data. I want to be able to create it in such a way that in one go I only display first 20 ids and there is a scroll bar that displays the next 20 ids and so on..
My code so far:
import matplotlib.pyplot as plt;
import numpy as np
fig,ax=plt.subplots(figsize=(100,2))
x=range(1,38)
y=[1]*len(x)
plt.bar(x,y,width=0.7,align='edge',color='green',ecolor='black')
for i,txt in enumerate(x):
ax.annotate(txt, (x[i],y[i]))
current=plt.gca()
current.axes.xaxis.set_ticks([])
current.axes.yaxis.set_ticks([])
plt.show()
and my output:
enter image description here
Matplotlib provides a Slider widget. You can use this to slice the array to plot and display only the part of the array that is selected.
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import numpy as np
fig,ax=plt.subplots(figsize=(10,6))
x=np.arange(1,38)
y=np.random.rand(len(x))
N=20
def bar(pos):
pos = int(pos)
ax.clear()
if pos+N > len(x):
n=len(x)-pos
else:
n=N
X=x[pos:pos+n]
Y=y[pos:pos+n]
ax.bar(X,Y,width=0.7,align='edge',color='green',ecolor='black')
for i,txt in enumerate(X):
ax.annotate(txt, (X[i],Y[i]))
ax.xaxis.set_ticks([])
ax.yaxis.set_ticks([])
barpos = plt.axes([0.18, 0.05, 0.55, 0.03], facecolor="skyblue")
slider = Slider(barpos, 'Barpos', 0, len(x)-N, valinit=0)
slider.on_changed(bar)
bar(0)
plt.show()

Contour plot from csv file with row being axis

I am trying to make a contour plot from a csv file. I would like the first column to be the x axis, the first row (with has values) to be the y, and then the rest of the matrix is what should be contoured, see the basic example in the figure below.
Simple table example
What I am really struggling is to get that first row to be the y axis, and then how to define that set of values so that they can be called into the contourf function. Any help would be very much appreciated as I am very new to python and am really don't know where to start with this problem.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import csv
import pandas as pd
import numpy as np
from csv import reader
from matplotlib import cm
f = pd.read_csv('/trialforplot.csv',dayfirst=True,index_col=0)
x = f.head()
y = f.columns
X,Y = np.meshgrid(x,y)
z=(x,y)
z=np.array(z)
Z=z.reshape((len(x),len(y)))
plt.contour(Y,X,Z)
plt.colorbar=()
plt.xlabel('Time')
plt.ylable('Particle Size')
plt.show()
I'm stuck at defining the z values and getting my contour plot plotting.

Lat/lon using Basemap and maskoceans getting mixed up after "for" loop

I am trying to identify the indices of the masked pixels when using
maskoceans
so I can then call only the land pixels in a code I have that is currently going through the whole globe, even though I do not care about the ocean pixels. I was trying different methods to do so, and noticed that my plots were looking really weird. Eventually, I realized that something was getting mixed up in my lat/lon indices, even though I am not actually touching them! Here is the code:
import numpy as np
import netCDF4
from datetime import datetime, timedelta
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import matplotlib.dates as mpldates
import heat_transfer_coeffs
from dew_interface import get_dew
from matplotlib.dates import date2num, num2date
import numpy as np
import netCDF4
import heat_transfer_coeffs as htc
from jug.task import TaskGenerator
import matplotlib.cm as cm
import mpl_toolkits
from mpl_toolkits import basemap
from mpl_toolkits.basemap import Basemap, maskoceans
np.seterr(all='raise')
# set global vars
ifile = netCDF4.Dataset('/Users/myfile.nc', 'r')
times = ifile.variables['time'][:].astype(np.float64) # hours since beginning of dataset
lats_1d = ifile.variables['latitude'][:] # 90..-90
lons_1d = ifile.variables['longitude'][:] # 0..360
lons_1d[lons_1d>180]-=360 #putting longitude into -180..180
lons, lats = np.meshgrid(lons_1d, lats_1d)
ntimes, nlats, nlons = ifile.variables['tm'].shape
ifile.close()
map1 = basemap.Basemap(resolution='c', projection='mill',llcrnrlat=-36 , urcrnrlat=10, llcrnrlon=5 , urcrnrlon=52)
#Mask the oceans
new_lon = maskoceans(lons,lats,lons,resolution='c', grid = 10)
new_lat = maskoceans(lons,lats,lats,resolution='c', grid = 10)
fig = plt.figure
pc = map1.pcolormesh(lons, lats, new_lat, vmin=0, vmax=34, cmap=cm.RdYlBu, latlon=True)
plt.show()
for iii in range(new_lon.shape[1]):
index = np.where(new_lon.mask[:,iii] == False)
index2 = np.where(new_lon.mask[:,iii] == True)
new_lon[index[0],iii] = 34
new_lon[index2[0],iii] = 0
fig = plt.figure
pc = map1.pcolormesh(lons, lats, new_lat, vmin=0, vmax=34, cmap=cm.RdYlBu, latlon=True)
plt.show()
The first figure I get shows the expected map of Africa with oceans masked and the land values corresponding to the latitude (until saturation of the colorbar at 34, but that value was just taken as an example)
However, the second figure, which should plot the exact same thing as the first one, comes out all messed up, even though the loop in between the first and second figure doesn't touch any of the parameters involved in plotting it:
If I comment out the loop in between figure 1 and 2, figure 2 looks just like figure 1. Any idea about what is going on here?
Short answer, your loop is modifying the variables lons and lats indirectly.
Explanation: the function maskoceans creates a masked array from input array. The masked array and the input array share the same data, so that lons and new_lon share the same data, same thing for lats and new_lat. This means that when you modify new_lon in your loop, you are also modifying lons. That is the source of your problem. The only difference is that new_lon and new_lat are associated with a mask that is used to choose valid data points.
Solution: Make a copy of the initial array before you call maskoceans. You can do that with:
import copy
lons1 = copy.copy(lons)
lats1 = copy.copy(lats)
Then you use lons1 and lats1 to call maskoceans.

Categories

Resources