I'm trying to recreate the above style of plotting using matplotlib.
The raw data is stored in a 2D numpy array, where the fast axis is time.
Plotting the lines is the easy bit. I'm trying to get the shaded-in areas efficiently.
My current attempt looks something like:
import numpy as np
from matplotlib import collections
import matplotlib.pyplot as pylab
#make some oscillating data
panel = np.meshgrid(np.arange(1501), np.arange(284))[0]
panel = np.sin(panel)
#generate coordinate vectors.
panel[:,-1] = np.nan #lazy prevents polygon wrapping
x = panel.ravel()
y = np.meshgrid(np.arange(1501), np.arange(284))[0].ravel()
#find indexes of each zero crossing
zero_crossings = np.where(np.diff(np.signbit(x)))[0]+1
#calculate scalars used to shift "traces" to plotting corrdinates
trace_centers = np.linspace(1,284, panel.shape[-2]).reshape(-1,1)
gain = 0.5 #scale traces
#shift traces to plotting coordinates
x = ((panel*gain)+trace_centers).ravel()
#split coordinate vectors at each zero crossing
xpoly = np.split(x, zero_crossings)
ypoly = np.split(y, zero_crossings)
#we only want the polygons which outline positive values
if x[0] > 0:
steps = range(0, len(xpoly),2)
else:
steps = range(1, len(xpoly),2)
#turn vectors of polygon coordinates into lists of coordinate pairs
polygons = [zip(xpoly[i], ypoly[i]) for i in steps if len(xpoly[i]) > 2]
#this is so we can plot the lines as well
xlines = np.split(x, 284)
ylines = np.split(y, 284)
lines = [zip(xlines[a],ylines[a]) for a in range(len(xlines))]
#and plot
fig = pylab.figure()
ax = fig.add_subplot(111)
col = collections.PolyCollection(polygons)
col.set_color('k')
ax.add_collection(col, autolim=True)
col1 = collections.LineCollection(lines)
col1.set_color('k')
ax.add_collection(col1, autolim=True)
ax.autoscale_view()
pylab.xlim([0,284])
pylab.ylim([0,1500])
ax.set_ylim(ax.get_ylim()[::-1])
pylab.tight_layout()
pylab.show()
and the result is
There are two issues:
It does not fill perfectly because I am splitting on the array indexes closest to the zero crossings, not the exact zero crossings. I'm assuming that calculating each zero crossing will be a big computational hit.
Performance. It's not that bad, given the size of the problem - around a second to render on my laptop, but i'd like to get it down to 100ms - 200ms.
Because of the usage case I am limited to python with numpy/scipy/matplotlib. Any suggestions?
Followup:
Turns out linearly interpolating the zero crossings can be done with very little computational load. By inserting the interpolated values into the data, setting negative values to nans, and using a single call to pyplot.fill, 500,000 odd samples can be plotted in around 300ms.
For reference, Tom's method below on the same data took around 8 seconds.
The following code assumes an input of a numpy recarray with a dtype that mimics a seismic unix header/trace definition.
def wiggle(frame, scale=1.0):
fig = pylab.figure()
ax = fig.add_subplot(111)
ns = frame['ns'][0]
nt = frame.size
scalar = scale*frame.size/(frame.size*0.2) #scales the trace amplitudes relative to the number of traces
frame['trace'][:,-1] = np.nan #set the very last value to nan. this is a lazy way to prevent wrapping
vals = frame['trace'].ravel() #flat view of the 2d array.
vect = np.arange(vals.size).astype(np.float) #flat index array, for correctly locating zero crossings in the flat view
crossing = np.where(np.diff(np.signbit(vals)))[0] #index before zero crossing
#use linear interpolation to find the zero crossing, i.e. y = mx + c.
x1= vals[crossing]
x2 = vals[crossing+1]
y1 = vect[crossing]
y2 = vect[crossing+1]
m = (y2 - y1)/(x2-x1)
c = y1 - m*x1
#tack these values onto the end of the existing data
x = np.hstack([vals, np.zeros_like(c)])
y = np.hstack([vect, c])
#resort the data
order = np.argsort(y)
#shift from amplitudes to plotting coordinates
x_shift, y = y[order].__divmod__(ns)
ax.plot(x[order] *scalar + x_shift + 1, y, 'k')
x[x<0] = np.nan
x = x[order] *scalar + x_shift + 1
ax.fill(x,y, 'k', aa=True)
ax.set_xlim([0,nt])
ax.set_ylim([ns,0])
pylab.tight_layout()
pylab.show()
The full code is published at https://github.com/stuliveshere/PySeis
You can do this easily with fill_betweenx
. From the docs:
Make filled polygons between two horizontal curves.
Call signature:
fill_betweenx(y, x1, x2=0, where=None, **kwargs) Create a PolyCollection filling the regions between x1 and x2 where where==True
The important part here is the where
argument.
So, you want to have x2 = offset
, and then have where = x>offset
For example:
import numpy as np
import matplotlib.pyplot as plt
fig,ax = plt.subplots()
# Some example data
y = np.linspace(700.,900.,401)
offset = 94.
x = offset+10*(np.sin(y/2.)*
1/(10. * np.sqrt(2 * np.pi)) *
np.exp( - (y - 800)**2 / (2 * 10.**2))
) # This function just gives a wave that looks something like a seismic arrival
ax.plot(x,y,'k-')
ax.fill_betweenx(y,offset,x,where=(x>offset),color='k')
ax.set_xlim(93,95)
plt.show()
You need to do fill_betweenx
for each of your offsets. For example:
import numpy as np
import matplotlib.pyplot as plt
fig,ax = plt.subplots()
# Some example data
y = np.linspace(700.,900.,401)
offsets = [94., 95., 96., 97.]
times = [800., 790., 780., 770.]
for offset, time in zip(offsets,times):
x = offset+10*(np.sin(y/2.)*
1/(10. * np.sqrt(2 * np.pi)) *
np.exp( - (y - time)**2 / (2 * 10.**2))
)
ax.plot(x,y,'k-')
ax.fill_betweenx(y,offset,x,where=(x>offset),color='k')
ax.set_xlim(93,98)
plt.show()
This is fairly easy to do if you have your seismic traces in SEGY format and/or txt format (you will need to have them in .txt format ultimately). Spent a long time finding the best method. Faily new to python and programming as well, so please be gentle.
For converting the SEGY file to a .txt file I used SeiSee (http://dmng.ru/en/freeware.html; don't mind the russian site, it is a legitimate program). For loading and displaying you need numpy and matplotlib.
The following code will load the seismic traces, transpose them, and plot them. Obviously you need to load your own file, change the vertical and horizontal ranges, and play around a bit with vmin and vmax. It also uses a grey colormap. The code will produce an image like this: http://goo.gl/0meLyz
import numpy as np
import matplotlib.pyplot as plt
traces = np.loadtxt('yourtracestxtfile.txt')
traces = np.transpose(traces)
seismicplot = plt.imshow(traces[3500:4500,500:900], cmap = 'Greys',vmin = 0,vmax = 1,aspect = 'auto') #Tip: traces[vertical range,horizontal range]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With