Animating a map of ocean temperature - python

I have 12 subplots showing changes in ocean temperature for the Celtic Sea. Each subplot is for a different month in the year.
import xarray as xa
import cmocean.cm as cm
import matplotlib.patches as mpatches
from matplotlib.patches import Patch
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import matplotlib.animation as ani
import seaborn as sns
import pandas as pd
import plotly.express as px
import pycountry
import cartopy.crs as ccrs
import cartopy.feature as cfeat
# =============================================================================
# Step 1: Get data
# =============================================================================
data_model = xa.open_dataset("PD_tavg_out_atlanticcentric.nc",decode_times = False)
sal_obs_data = xa.open_dataset("sal_obs_CS_all.nc",decode_times = False)
temp_obs_data = xa.open_dataset("temp_obs_CS_all.nc",decode_times = False)
data_obs = xa.merge([sal_obs_data,temp_obs_data])
data_obs = data_obs.rename(lon = "longitude",
lat = "latitude")
# =============================================================================
# Step 2: Define Coordinates
# =============================================================================
long_max = 122.4
long_min = 100.8
lat_max = 25.2
lat_min = 0
dep_max = 100
dep_min = 0
tim_max = 35406.0
tim_min = 35405.0
def extract_shelf_sea(long_max, long_min,
lat_max, lat_min,
dep_max, dep_min,
tim_max, tim_min):
# =============================================================================
# Step 3: Extract data
# =============================================================================
extract_model_data = data_model.sel(longitude = slice(long_min,long_max),
latitude = slice(lat_min,lat_max),
depth = slice(dep_min,dep_max),
time = slice(tim_min,tim_max))
extract_obs_data = data_obs.sel(time = data_obs.time,
longitude = data_obs.longitude,
latitude = data_obs.latitude,
depth = extract_model_data.depth, method="nearest")
# =============================================================================
# Step 4: Calculating the difference in salinity and temperature
# =============================================================================
model_diff = extract_model_data.diff("depth")
obs_diff = extract_obs_data.diff("depth")
return model_diff, obs_diff
# =============================================================================
# Step 5: Plotting
# =============================================================================
def plot_SCS():
CS_model, CS_obs = extract_shelf_sea(2,-17,65,45,100,0,35405.95,35405)
map_proj = ccrs.Orthographic(central_longitude = -8.5, central_latitude = 55)
# =============================================================================
# Observational Data (temperature)
# =============================================================================
temp_obs = CS_obs.temp
map_proj = ccrs.Orthographic(central_longitude = -8.5, central_latitude = 55)
plot = temp_obs.plot(cmap = cm.curl,
cbar_kwargs = {'label':'Change in temperature between 15m and 82m (°C)'},
col='time', col_wrap=4,
transform = ccrs.PlateCarree(),
subplot_kws={"projection":map_proj},
vmin=-6, vmax=6)
plot.fig.suptitle("Celtic Sea - Observational Data")
for ax, title in zip(plot.axes.flat, ['Jan','Feb','Mar','Apr','May','Jun',
'Jul','Aug','Sep','Oct','Nov','Dec']):
ax.set_title(title)
for ax in plot.axes.flat:
ax.add_feature(cfeat.LAND,zorder=100,edgecolor="k",facecolor="gray")
I'd like to animate this, so I only have a single plot, but every second or so it moves to the next month. I've tried a few things with plotly and the matplotlib animation and haven't had any success.
I don't even really know what else to try. Any advice helpful!
Sorry, I can't attach the full original dataset as it's a huge file.

Your case is excellent for the use of ArtistAnimation, i.e., the flipbook approach using precomputed images. Sample code because the format of your animation is not specified:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, axes = plt.subplots(ncols=6, nrows=3, figsize=(15, 10))
#get specs for large image
gs = axes[0, -2].get_gridspec()
#remove unnecessary axis objects
for ax in axes[0:, -2:].flat:
ax.remove()
#update axes list and label all static images
axes = fig.get_axes()
for i, ax in enumerate(axes):
ax.axis("off")
ax.set_title(f"month {i+1}")
#add axis object for large, animated image
ax_large = fig.add_subplot(gs[0:, -2:])
ax_large.axis("off")
#fake images
def f(x, y, i):
return np.sin(x*i/4) * i/6 + np.cos(y* (12-i)/4)
x = np.linspace(0, 2 * np.pi, 80)
y = np.linspace(0, 2 * np.pi, 120).reshape(-1, 1)
all_ims = []
min_v = -3
max_v = 3
ani_cmap = "seismic"
for i, ax_small in enumerate(axes):
#image generation unnecessary for you because your images already exist
arr = f(x, y, i)
#static image into small frame
im_small = ax_small.imshow(arr, vmin=min_v, vmax=max_v, cmap=ani_cmap)
#animated image into large frame
im_large = ax_large.imshow(arr, animated=True, vmin=min_v, vmax=max_v, cmap=ani_cmap)
#animated images are collected in a list
all_ims.append([im_large])
ani = animation.ArtistAnimation(fig, all_ims, interval=200, blit=True)
plt.show()
Sample output:

Related

Animated scatter plot rendering points in wrong order

I have a bunch of data points each associated with a time, and I want to render them in an animation where I fade points in and out based on this time. The problem I'm running into is that the points are being rendered out of order. I've reduced the problem down to the minimal failing example below. Any help in figuring out the cause would be greatly appreciated.
import os
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation
from matplotlib.animation import PillowWriter
from matplotlib.animation import FFMpegWriter
def plot_data_3d_video_time(
data: np.ndarray,
steps: np.ndarray,
directory: str = './',
filename: str = 'video.webm',
fps: int = 30):
""" Produce a video of a 3D scatter plot varying the opacity of the points based on their time step. """
max_alpha = 1.0
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
artists = []
for t in range(steps.max()):
artists.append([])
alpha = steps == t
points = data[alpha,:] # Point that should be rendered
alpha = alpha * max_alpha
print(f'{points}')
a = ax.scatter(data[:,0], data[:,1], data[:,2],
alpha=alpha,
c='b'
)
artists[-1].append(a)
# This is what I expect to see. Why is it showing a different point?
artists[-1].append(
ax.scatter(
[points[0,0]], [points[0,1]], [points[0,2]],
c='r', marker='x', s=100
)
)
# Code for saving the video. Can be ignored.
video_format = filename.split('.')[-1]
filename = os.path.join(directory, filename)
animation = ArtistAnimation(plt.gcf(), artists, interval=50, blit=True)
if video_format == 'gif':
writer = PillowWriter(fps=fps)
elif video_format == 'mp4':
writer = FFMpegWriter(fps=fps)
elif video_format == 'webm':
writer = FFMpegWriter(fps=fps, codec='libvpx-vp9')
else:
raise ValueError('Unknown video format: {}'.format(video_format))
animation.save(filename, writer=writer)
print(f'Saved video to {os.path.abspath(filename)}')
plt.close()
if __name__ == '__main__':
# Generate some data
n = 10
steps = np.arange(n)
data0 = np.arange(n)*0#[::-1]
data1 = np.arange(n)*0#[::-1]
data2 = np.arange(n)[::-1]
data = np.stack([data0, data1, data2], axis=1)
# Plot the data
plot_data_3d_video_time(data=data, steps=steps, filename='test.gif', fps=5)
In this code, I'm plotting the real data as a blue circle and added a debugging point rendered as a red X. This X is where I expect the blue circle to be. The resulting video from the code above is
If I set change the input data by changing the data2 = np.arange(n)[::-1] line to data2 = np.arange(n), then both the blue circle and red X coincide.
I haven't been able to find a clear pattern on where it works and where it fails. Other setups where it's reversed:
data0 = np.arange(n)[::-1]
data1 = np.arange(n)[::-1]
data2 = np.arange(n)[::-1]
data0 = np.arange(n)*0
data1 = np.arange(n)
data2 = np.arange(n)*0

How to plot the map correctly over the SST data in cartopy?

I am trying to plot L2 Sea Surface Temperature data and I want to plot it over the globe in a geostationary projection. I tried the following code:
import h5py
import sys
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# First get data from HDF5 file with h5py:
fn = '/home/swadhin/project/insat/data/3RIMG_30MAR2018_0014_L2B_SST_V01R00.h5'
with h5py.File(fn) as f:
print(list(f.keys()))
image = 'SST'
img_arr = f[image][0,:,:]
# get _FillValue for data masking
img_arr_fill = f[image].attrs['_FillValue'][0]
# retrieve extent of plot from file attributes:
left_lon = f.attrs['left_longitude'][0]
right_lon = f.attrs['right_longitude'][0]
lower_lat = f.attrs['lower_latitude'][0]
upper_lat = f.attrs['upper_latitude'][0]
sat_long = f.attrs['Nominal_Central_Point_Coordinates(degrees)_Latitude_Longitude'][1]
sat_hght = f.attrs['Nominal_Altitude(km)'][0] * 1000.0 # (for meters)
print('Done reading HDF5 file')
## Use np.ma.masked_equal with integer values to
## mask '_FillValue' data in corners:
img_arr_m = np.ma.masked_equal(img_arr, img_arr_fill)
print(img_arr_fill)
print(np.max(img_arr_m))
print(np.min(img_arr_m))
#print(np.shape(img_arr_m))
# # Create Geostationary plot with cartopy and matplotlib
map_proj = ccrs.Geostationary(central_longitude=sat_long,satellite_height=sat_hght)
ax = plt.axes(projection=map_proj)
ax.coastlines(color='black',linewidth = 0.5)
#ax.add_feature(cfeature.BORDERS, edgecolor='white', linewidth=0.25)
#ax.add_feature(cfeature.STATES,edgecolor = 'red',linewidth = 0.5)
ax.gridlines(color='black', alpha=0.5, linestyle='--', linewidth=0.75, draw_labels=True)
#ax.add_geometries(ind_shapes,crs = map_proj, edgecolor = 'black', alpha = 0.5)
map_extend_geos = ax.get_extent(crs=map_proj)
plt.imshow(img_arr_m, interpolation='none',origin='upper',extent=map_extend_geos, cmap = 'jet')
plt.colorbar()
#plt.clim(-10,5)
plt.savefig('/home/swadhin/project/insat/data/l2_sst.png',format = 'png', dpi=1000)
The output I got is not very accurate. There are some SST values over some of the land areas which should not be the case.
I am adding the data here for people who wanna give it a try.
https://drive.google.com/file/d/126oW36JXua-zz3XMUcyZxwPj8UISDgUM/view?usp=sharing
I have checked your HDF5 file, and there are Longitude and Latitude variables in the file. So I think these WGS84 coordinates should be used.
First, the imshow method needs the image boundary information that cannot be obtained.
I also tried the pcolormesh method, but this method can not accept lon/lat array with NaN value.
In conclusion, the contourf seems to be the best choice, but this method still has the disadvantage that it is time-consuming to run.
import h5py
import sys
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
fn ='3RIMG_30MAR2018_0014_L2B_SST_V01R00.h5'
with h5py.File(fn) as f:
print(list(f.keys()))
image = 'SST'
img_arr = f[image][0,:,:]
lon = f['Longitude'][:]*0.01
lat = f['Latitude'][:]*0.01
# # get _FillValue for data masking
img_arr_fill = f[image].attrs['_FillValue'][0]
# # retrieve extent of plot from file attributes:
left_lon = f.attrs['left_longitude'][0]
right_lon = f.attrs['right_longitude'][0]
lower_lat = f.attrs['lower_latitude'][0]
upper_lat = f.attrs['upper_latitude'][0]
sat_long = f.attrs['Nominal_Central_Point_Coordinates(degrees)_Latitude_Longitude'][1]
sat_hght = f.attrs['Nominal_Altitude(km)'][0] * 1000.0 # (for meters)
print('Done reading HDF5 file')
## Use np.ma.masked_equal with integer values to
## mask '_FillValue' data in corners:
img_arr_m = np.ma.masked_equal(img_arr, img_arr_fill)
print(img_arr_fill)
print(np.max(img_arr_m))
print(np.min(img_arr_m))
lon_m = np.ma.masked_equal(lon, 327.67)
lat_m = np.ma.masked_equal(lat, 327.67)
# # Create Geostationary plot with cartopy and matplotlib
map_proj = ccrs.Geostationary(central_longitude=sat_long,satellite_height=sat_hght)
# or map_proj = ccrs.PlateCarree()
ax = plt.axes(projection=map_proj)
ax.set_global()
ax.coastlines(color='black',linewidth = 0.5)
ax.add_feature(cfeature.BORDERS, edgecolor='white', linewidth=0.25)
ax.add_feature(cfeature.STATES,edgecolor = 'red',linewidth = 0.5)
ax.gridlines(color='black', alpha=0.5, linestyle='--', linewidth=0.75, draw_labels=True)
cb = ax.contourf(lon_m,lat_m,img_arr_m, cmap = 'jet',transform = ccrs.PlateCarree())
plt.colorbar(cb)
plt.savefig('l2_sst1.png',format = 'png', dpi=300)
Here is the output figure.
or using a lon-lat projection.

Removing noise from smoothened graph

I have this power spectrum that I am trying to find the Nu_max in. I have done so by using a gaussian filter, however, I have some noise in the start of the signal.
Is there somehow to balance this or remove it from my curve?
Or a way to remove this, or at least make it better?
Since it is obviously affecting Nu_Max.
import matplotlib.pyplot as plt
import pandas as pd
from scipy.ndimage import gaussian_filter[enter image description here][1]
from scipy.signal import find_peaks
import numpy as np
# Importing data
power_list = pd.read_table(r'G:\Downloads\New_power_list_KIC.txt')
frequency_list = pd.read_table(r'G:\Downloads\List_Frequency.txt')
# Gaussian filter
df = gaussian_filter(power_list, sigma=1800)
List = np.linspace(0, 141699, num = int(141699) , endpoint = True)
for i in range(141699):
List[i] = df[i]
# Finding peaks
peaks = find_peaks(List, height =0, threshold = None, distance = None, prominence = 3, width = None)
print(peaks[1])
print(peaks[1].keys() )
height = peaks[1]['peak_heights'] #List of heights
peak_pos = frequency_list.iloc[peaks[0]] #List of peak pos
data_peak = peak_pos
# Plotting
fig = plt.figure()
ax = fig.subplots()
# plt.ylim([0,100])
# plt.xlim([115,120])
# plt.ylim([0,90])
# plt.xlim([70,75])
# plt.ylim([0,120])
# plt.xlim([75,80])
# plt.ylim([0,150])
# plt.xlim([85,90])
# plt.ylim([0,150])
# plt.xlim([95,100])
# plt.ylim([0,80])
# plt.xlim([105,110])
# plt.ylim([0,50])
# plt.xlim([100,120])
plt.xlim([0,130])
plt.ylim([0,100])
plt.xlabel('Frequeny(μHz)')
plt.ylabel('Power density p.p.m')
ax.plot(frequency_list*1E6, df-10, 'k', linewidth=.5)
# ax.scatter(peak_pos*1E6, height, c='r', s = 10, marker = 'D', label = 'Maxima')
ax.legend()
ax.grid()
plt.show()
Smoothened graph

Matplotlib animated histogram colormap/gradient

I am trying to animate a histogram using matplotlib and I want to show the different bars using a colormap, e.g:
I have this working when I clear the complete figure every frame and then redraw everything. But this is very slow, so I am trying out the example by matplotlib itself.
This works and is very fast, but unfortunately I have no idea on how to specify a colormap because it is using the patches.PathPatch object to draw the histogram now. I can only get it to work with the same single color for every individual bar.
How can I specify a gradient or colormap to achieve the desired result shown above?
Here is an example of a working animation with a single color which I am currently using.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.animation as animation
# Fixing random state for reproducibility
np.random.seed(19680801)
# histogram our data with numpy
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
nrects = len(left)
nverts = nrects * (1 + 3 + 1)
verts = np.zeros((nverts, 2))
codes = np.ones(nverts, int) * path.Path.LINETO
codes[0::5] = path.Path.MOVETO
codes[4::5] = path.Path.CLOSEPOLY
verts[0::5, 0] = left
verts[0::5, 1] = bottom
verts[1::5, 0] = left
verts[1::5, 1] = top
verts[2::5, 0] = right
verts[2::5, 1] = top
verts[3::5, 0] = right
verts[3::5, 1] = bottom
patch = None
def animate(i):
# simulate new data coming in
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
top = bottom + n
verts[1::5, 1] = top
verts[2::5, 1] = top
return [patch, ]
fig, ax = plt.subplots()
barpath = path.Path(verts, codes)
patch = patches.PathPatch(
barpath, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)
plt.show()
I recommend u using BarContainer, you can change bar color individually. In your example, the path is single object, matplotlib seems not to support gradient color for a single patch (not sure though).
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
# histogram our data with numpy
data = np.random.randn(1000)
colors = plt.cm.coolwarm(np.linspace(0, 1, 100))
def animate(i):
data = np.random.randn(1000)
bc = ax.hist(data, 100)[2]
for i, e in enumerate(bc):
e.set_color(colors[i])
return bc
fig, ax = plt.subplots(1, 1, figsize=(7.2, 7.2))
ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)

how to animate an image derived from a 2d histogram

I am trying to create an animation of a scatterplot as well as a 2d Histogram. I can get the scatter plot working. I can also create individual stills of the 2d Histogram but cannot get it to animate with the scatter plot.
I can create some mock data if that would help. Please find code below.
import numpy as np
import matplotlib.pyplot as plt
import csv
import matplotlib.animation as animation
#Create empty lists
visuals = [[],[],[]]
#This dataset contains XY coordinates from 21 different players derived from a match
with open('Heatmap_dataset.csv') as csvfile :
readCSV = csv.reader(csvfile, delimiter=',')
n=0
for row in readCSV :
if n == 0 :
n+=1
continue
#All I'm doing here is appending all the X-Coordinates and all the Y-Coordinates. As the data is read across the screen, not down.
visuals[0].append([float(row[3]),float(row[5]),float(row[7]),float(row[9]),float(row[11]),float(row[13]),float(row[15]),float(row[17]),float(row[19]),float(row[21]),float(row[23]),float(row[25]),float(row[27]),float(row[29]),float(row[31]),float(row[33]),float(row[35]),float(row[37]),float(row[39]),float(row[41]),float(row[43])])
visuals[1].append([float(row[2]),float(row[4]),float(row[6]),float(row[8]),float(row[10]),float(row[12]),float(row[14]),float(row[16]),float(row[18]),float(row[20]),float(row[22]),float(row[24]),float(row[26]),float(row[28]),float(row[30]),float(row[32]),float(row[34]),float(row[36]),float(row[38]),float(row[40]),float(row[42])])
visuals[2].append([1,2])
#Create a list that contains all the X-Coordinates and all the Y-Coordinates. The 2nd list indicates the row. So visuals[1][100] would be the 100th row.
Y = visuals[1][0]
X = visuals[0][0]
fig, ax = plt.subplots(figsize = (8,8))
plt.grid(False)
# Create scatter plot
scatter = ax.scatter(visuals[0][0], visuals[1][0], c=['white'], alpha = 0.7, s = 20, edgecolor = 'black', zorder = 2)
#Create 2d Histogram
data = (X, Y)
data,x,y,p = plt.hist2d(X,Y, bins = 15, range = np.array([(-90, 90), (0, 140)]))
#Smooth with filter
im = plt.imshow(data.T, interpolation = 'gaussian', origin = 'lower', extent = [-80,80,0,140])
ax.set_ylim(0,140)
ax.set_xlim(-85,85)
#Define animation.
def animate(i) :
scatter.set_offsets([[[[[[[[[[[[[[[[[[[[[visuals[0][0+i][0], visuals[1][0+i][0]], [visuals[0][0+i][1], visuals[1][0+i][1]], [visuals[0][0+i][2], visuals[1][0+i][2]], [visuals[0][0+i][3], visuals[1][0+i][3]], [visuals[0][0+i][4], visuals[1][0+i][4]],[visuals[0][0+i][5], visuals[1][0+i][5]], [visuals[0][0+i][6], visuals[1][0+i][6]], [visuals[0][0+i][7], visuals[1][0+i][7]], [visuals[0][0+i][8], visuals[1][0+i][8]], [visuals[0][0+i][9], visuals[1][0+i][9]], [visuals[0][0+i][10], visuals[1][0+i][10]], [visuals[0][0+i][11], visuals[1][0+i][11]], [visuals[0][0+i][12], visuals[1][0+i][12]], [visuals[0][0+i][13], visuals[1][0+i][13]], [visuals[0][0+i][14], visuals[1][0+i][14]], [visuals[0][0+i][15], visuals[1][0+i][15]], [visuals[0][0+i][16], visuals[1][0+i][16]], [visuals[0][0+i][17], visuals[1][0+i][17]], [visuals[0][0+i][18], visuals[1][0+i][18]], [visuals[0][0+i][19], visuals[1][0+i][19]], [visuals[0][0+i][20], visuals[1][0+i][20]]]]]]]]]]]]]]]]]]]]]])
# This is were I'm having trouble...How do I animate the image derived from the 2d histogram
im.set_array[i+1]
ani = animation.FuncAnimation(fig, animate, np.arange(0,1000),
interval = 100, blit = False)
The image can be updated with im.set_data(data), where you need to call hist2d to get the updated data to pass to im. As a minimal example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
X = np.random.randn(100000)
Y = np.random.randn(100000) + 5
fig, ax = plt.subplots(figsize = (8,8))
#Create 2d Histogram
data,x,y = np.histogram2d(X,Y, bins = 15)
#Smooth with filter
im = plt.imshow(data.T, interpolation = 'gaussian', origin = 'lower')
#Define animation.
def animate(i) :
X = np.random.randn(100000)
Y = np.random.randn(100000) + 5
data,x,y = np.histogram2d(X,Y, bins = 15)
im.set_data(data)
ani = animation.FuncAnimation(fig, animate, np.arange(0,1000),
interval = 100, blit = False)
plt.show()

Categories

Resources