My matplotlib-pillow animation flask app in cpanel python app deployment displays broken images when I access my flask definitions with the Play, Reset, and Save Figure javascript buttons on the html file. The rest of the html renders and operates correctly.
I tested this on my windows home server (127.0.0.0:5000). It rendered everything correctly, but not on the CPanel correctly. The CPanel environment is in a passenger-wsgi.py that I cannot edit (I have tried). I realize that the send _file might be the problem. I have tried the FileWrapper and flask.Response combo suggested here for the PythonAnywhere solution thinking this might apply to the CPanel hosting. I have tried to directly return the base64 with ut-8 encode solution from the update and save_fig definations in my app.py. The plots continue to not render for any of these solutions.
Here is my app.py
rom werkzeug.wsgi import FileWrapper
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from io import BytesIO
import base64
from PIL import Image
import os
import numpy as np
app = Flask(__name__)
numpoints=20
def constants(x0,y0,v0,theta):
global v0st
v0st=str(v0)
ay=-9.81
theta0=np.radians(theta)
vx0=v0*np.cos(theta0)
vy0=v0*np.sin(theta0)
yrange=((vy0**2)/(2*-ay))+y0
trange=(vy0/-ay)+(np.sqrt(((vy0/-ay)**2)+((2*y0)/-ay)))
xrange=(trange*vx0)+x0
xconst_list=[x0,vx0]
yconst_list=[y0,vy0,ay]
return [xconst_list,yconst_list,trange,xrange,yrange,theta]
def x(t,c):
x=(c[1]*t)+ c[0]
return x
def y(t,c):
y=((c[2]*(t**2))/2)+(c[1]*t)+c[0]
return y
def line_int(con):
ca=con
num=int(ca[2]*numpoints)
tm=np.linspace(0,ca[2],num)
# Initialize the plot
global fig
fig, ax = plt.subplots()
line0, = ax.plot(x(tm,const[0]),y(tm,ca[1]),label=str(const[5])+" Degrees")
ax.set_xlim(0.0, ca[3])
ax.set_ylim(0.0, ca[4])
plt.title("Projectile Motion (Initial Velocity="+v0st+" m/s)")
plt.xlabel("x-aixs (m)")
plt.ylabel("y-axis (m)")
ax.legend([line0],[line0.get_label()])
return line0
# Function to update the plot in each frame of the animation
def update(frame,line,con):
ca=con
newline=line
num=int(ca[2]*numpoints)
tm=np.linspace(0,ca[2],num)
newline.set_xdata(x(frame/numpoints - tm,ca[0]))
newline.set_ydata(y(frame/numpoints - tm,ca[1]))
return line
# Function to generate and encode the plot as a base64 string
def generate_plot():
buffer = BytesIO()
num=int(const[2]*numpoints)
img_paths=[]
# Save individual frames as PNG images
for frame in range(10):
update(frame,line1,const)
fig.savefig(buffer, format='png')
buffer.seek(0)
# Convert PNG to RGB image
img = Image.open(buffer)
img = img.convert('RGB')
pathf='/home/airandspace/projectiledemo/static/'+f'frame_{frame}.png'
img_paths.append(pathf)
img.save(pathf)
# Create a GIF from the saved frames
img = Image.open(img_paths[0])
img.save(buffer, format='GIF', save_all=True, append_images=[Image.open(img_path) for img_path in img_paths[1:]], duration=num, loop=0)
# Close and remove temporary PNG files
for img_path in img_paths:
os.remove(img_path)
plt.close(fig)
buffer.seek(0)
encoded_plot = base64.b64encode(buffer.getvalue()).decode('utf-8')
return f'data:image/gif;base64,{encoded_plot}'
# Route to render the HTML page
@app.route('/')
def index():
return render_template('index6.html')
@app.route("/demo", methods=["POST", "GET"])
def demo():
if request.method == "POST":
xi=float(request.form["xint"])
yi=float(request.form["yint"])
vi=float(request.form["velocity"])
thetai=float(request.form["angle"])
global const
const=constants(xi,yi,vi,thetai)
global line1
line1=line_int(const)
num=int(const[2]*numpoints)
return render_template("plot6.html",fig=generate_plot(),nump=num )
else:
return render_template("index6.html")
# Route to handle dynamic updates of the w
@app.route('/update_plot/<int:frame>')
def update_plot(frame):
buffer = BytesIO()
update(frame,line1,const)
fig.savefig(buffer, format='png')
plt.close(fig)
buffer.seek(0)
return send_file(buffer,mimetype='image/png')
# Route to handle slider updates
@app.route('/update_slider', methods=['POST'])
def update_slider():
frame = int(request.form['frame'])
buffer = BytesIO()
update(frame,line1,const)
fig.savefig(buffer, format='png')
plt.close(fig)
buffer.seek(0)
return send_file(buffer,mimetype='image/png')
# Route to handle saving the current figure
@app.route('/save_fig')
def save_fig():
frame = int(request.args.get('frame', 0))
update(frame,line1,const)
# Save the current figure as a PNG image
buffer = BytesIO()
fig.savefig(buffer,format='png')
buffer.seek(0)
# Send the saved figure as a downloadable file
return send_file(buffer, mimetype='image/png', as_attachment=True, download_name=f'figure_frame_{frame}.png')
# Start the Flask app
if __name__ == '__main__':
app.run(debug=True)
my plot6.html
<!DOCTYPE html>
{% extends 'base.html' %}
{%block content %}
<h3>Your Projectile!</h3>
<div id="plot-container">
<img id="plot-image" src="{{ fig }}" alt="Plot">
</div>
<label for="frame-slider">Frame:</label>
<input type="range" id="frame-slider" min="0" max="num" value="0" oninput="updateSlider(this.value)">
<p>Frame: <span id="frame-value">0</span></p>
<button onclick="startAnimation()">Play</button>
<button onclick="stopAnimation()">Stop</button>
<button onclick="resetAnimation()">Reset</button>
<button onclick="saveFig()">Save Fig</button>
<script>
var frame = 0;
var intervalId;
function startAnimation() {
intervalId = setInterval(function() {
updateSlider(frame);
frame += 1;
document.getElementById('plot-image').src = `/update_plot/${frame}`;
}, 1000);
}
function stopAnimation() {
clearInterval(intervalId);
}
function resetAnimation() {
frame = 0;
document.getElementById('plot-image').src = `/update_plot/${frame}`;
updateSlider(frame);
}
function saveFig() {
var link = document.createElement('a');
link.href = `/save_fig?frame=${frame}`;
link.download = `figure_frame_${frame}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Manually set the slider value to match the saved frame
document.getElementById('frame-slider').value = frame;
document.getElementById('frame-value').innerText = frame;
}
function updateSlider(value) {
frame = parseInt(value);
document.getElementById('frame-slider').value = frame;
document.getElementById('frame-value').innerText = frame;
}
</script>
{% endblock %}
and my baset.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css')}}">
<title>Projectile Demonstration</title>
</head>
<body>
<table>
<tr>
<th colspan="2"><h1>Projectile Motion Demonstration</h1>
<h2>Enter the properties of the projectile!</h2>
</th>
<tr>
<td>
{% block nav %}
<table>
<form action="{{url_for("demo")}}" method="post">
<div class="form-group">
<tr>
<td><label for="Angle">Angle (degrees))</label></td>
<td><input type="text" id="Angle" name="angle" placeholder="Enter the angle."></td>
</tr>
<tr>
<td><label for="Velocity">Velocity (m/s)</label></td>
<td><input type="text" id="Velocity" name="velocity" placeholder="Enter the velocity."></td>
</tr>
<tr>
<td><label for="InitialX">Initial X position (m)</label></td>
<td><input type="text" id="IntialX" name="xint" placeholder="Enter the initial x position."></td>
</tr>
<tr>
<td><label for="InitialY">Initial Y position(m)</label></td>
<td><input type="text" id="InitialY" name="yint" placeholder="Enter the initial y position."></td>
</tr>
</div>
<tr>
<td><button class=green type="submit">Submit</button><button class=green type="reset">Reset</button>
<td>
</tr>
</form>
<tr>
<td>{% block submit %}{% endblock %}</td>
</tr>
</table>
{% endblock %}
</td>
<td>
{% block content %}{% endblock %}
</td>
</tr>
<tr>
<td colspan="2"> <p> This demonstration shows the projectile motion of object launched at from a position (<math><mrow><msub><mi>x</mi><mi>o</mi></msub><mo>,</mo><msub><mi>y</mi><mi>o</mi></msub></mrow></math>) with initial velocity (<math><mrow><msub><mi>v</mi><mi>o</mi></msub></mrow></math>) at an angle (<math><mi>θ</mi></math>). Here are the <a href= "{{url_for('static', filename='projectilemotion.pdf')}}">equations</a> we use for this demonstration.</p>
</td>
</tr>
</table>
</body>
</html>