Edit: 2026-08-29
<script src="/js/fft_demo.js"></script> <script src="https://cdn.plot.ly/plotly-3.7.0.min.js"></script>1) the first file contains my JS glue which used to send HTML messages to python, and receive the response
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>FFT Demo (via Python)</title>
<link href="css/nsr-20170909.css" rel="stylesheet" type="text/css">
<script src="/js/fft_demo.js"></script>
<script src="https://cdn.plot.ly/plotly-3.7.0.min.js"></script>
<!--
<script src="https://d3js.org/d3.v5.js"></script>
<script src="https://mpld3.github.io/js/mpld3.v0.5.10.js"></script>
-->
<style>
body {font-family: monospace; font-weight:bold;border:1px solid gray;padding:4px;}
.r {color:red}
</style>
</head>
<body>
<h4>FFT Web Demo (Python via CGI)</h4>
<form id="form1">
<div style="width:400px;text-align:right">
Frequency Amplitude <br>
Wave 1: <input id="w1" type="checkbox">
<input id="f1" size="12" maxlength="12" type="text">
<input id="a1" size="12" maxlength="12" type="text"><br>
Wave 2: <input id="w2" type="checkbox">
<input id="f2" size="12" maxlength="12" type="text">
<input id="a2" size="12" maxlength="12" type="text"><br>
Wave 3: <input id="w3" type="checkbox">
<input id="f3" size="12" maxlength="12" type="text">
<input id="a3" size="12" maxlength="12" type="text"><br>
<button type="button" id="but1" onclick="setDefaults1();">Defaults 1</button>
<button type="button" id="but2" onclick="setDefaults2();">Defaults 2</button>
<button type="button" id="but3" onclick="setDefaults3();">Defaults 3</button>
<button type="button" id="but4" onclick="runDemo();">Run Demo</button>
<button type="button" id="but5" onclick="doReset();">Reset</button>
</div>
<hr>interface message:<br>
<div id="msg" style="width:396px;height:40px;padding:2px;border:1px solid gray;display:inline-block;"> </div>
</form>
<div id="pix_buffer1"></div>
<hr>
<div style="font-family:Calibri, Arial, sans-serif;font-weight:normal">
<p>Neil Rieck<br>
Waterloo, Ontario, Canada.<br>
<a href="https://neilrieck.net" target="_blank">https://neilrieck.net</a></p>
<p><span class="r">Caveat:</span> This demo occasionally breaks at this hobbyist site. If you notice any problems
then please send me an email here: <a href="MAILTO:n.rieck@bell.net">n.rieck@bell.net</a></p>
<hr>
<div style="font-weight:700">Additional information for inquiring minds</div>
<ul>
<li>Overview
<ul>
<li>The FFT (Fast Fourier Transform) is an algorithm for converting signals from the time-domain
(think oscilloscope) to the frequency-domain</li>
<li>The <a href="https://en.wikipedia.org/wiki/Fourier_series" target="_blank">Fourier series</a> was first
published by <a href="https://en.wikipedia.org/wiki/Joseph_Fourier"
target="_blank">Joseph Fourier</a> in 1822 (original work purportedly done in Egypt while serving under
Napoleon)<br>
Note: Daniel Bernoulli and Leonhard Euler worked on this branch of mathematics before Fourier, but did not
publish (or properly publish)</li>
<li>The <a href="https://en.wikipedia.org/wiki/Fast_Fourier_transform" target="_blank">Fast Fourier Transform</a>
was first published in 1965 by J. W. Cooley and John Tukey while working at the research division of IBM<br>
Note: While working on a theory for planetary orbits, Carl Gauss proposed a solution very close to the 1965
FFT publication.</li>
</ul>
</li>
<li>Source Code:
<ul>
<li><a href="https://neilrieck.net/docs/python_notes.html" target="_blank">python: notes</a> - for people new
to python programming</li>
</ul>
</li>
</ul>
</div>
</body>
</html>
// ===========================================================================
// title : fft_demo.js
// author : Neil Rieck
// created: 2025-05-12
// edit : 2025-08-29
// notes :
// 1) this SPA (single page application) empoys AJAX (Asynchronous Javascript And XML)
// 2) this demo employs plain JavaScript in order to make things clear to you
// 3) a real world application would employ jQuery with either ReactJS or AngularJS
// 4) this demo employs HTTP GET. A real world application would use HTTP POST
// ===========================================================================
var state=0; // ajax state
var timeOutId1=0; // timer ID
var debugFlag=1; // enable to develop
var f1=""; // frequency
var f2=""; //
var f3=""; //
var a1=""; // amplitude
var a2=""; //
var a3=""; //
var w1=false; // wave
var w2=false; //
var w3=false; //
var SECS=15; //
var MS=SECS*1000; //
var nsr_domain=window.location.hostname; // server address
var nsr_protocol=window.location.protocol; // http or https
//
// called by the RESET button
//
function doReset(){
state=0;
if (timeOutId1!=0){
clearTimeout(timeOutId1);
timeOutId1=0;
}
for (i=1; i<=3; i++){
document.getElementById("f"+i).value="";
document.getElementById("a"+i).value="";
document.getElementById("w"+i).checked=false;
}
clearPixBuffer();
writeMsg("Reset Done");
}
function clearPixBuffer(){
try{
document.getElementById("pix_buffer1").innerHTML = "";
}catch(e){
console.log("clearPixBuffer-e-",e);
}
}
function writeMsg(x){
console.log("writeMsg:",x);
// document.getElementById("msg").value=x; // if element is INPUT
document.getElementById("msg").innerHTML=x; // if element is DIV
console.log("writeMsg: done");
}
//
// called by the "SET DEFAULTS 1" button
//
function setDefaults1() {
send_ajax("setDefaults1");
}
//
// called by the "SET DEFAULTS 2" button
//
function setDefaults2() {
send_ajax("setDefaults2");
}
//
// called by the "SET DEFAULTS 3" button
//
function setDefaults3() {
send_ajax("setDefaults3");
}
//
// read form fields
//
function readFields(){
f1 = document.getElementById("f1").value;
f2 = document.getElementById("f2").value;
f3 = document.getElementById("f3").value;
a1 = document.getElementById("a1").value;
a2 = document.getElementById("a2").value;
a3 = document.getElementById("a3").value;
w1 = document.getElementById("w1").checked;
w2 = document.getElementById("w2").checked;
w3 = document.getElementById("w3").checked;
}
//
// called by the RUN DEMO button
//
function runDemo() {
score=0;
readFields();
try{
for (i=1; i<=3; i++){
if (w1===true) score++;
if (w2===true) score++;
if (w3===true) score++;
}
}catch{
score=-1;
}
if (score<0){
writeMsg("error, bad data detected");
return;
}
if (score==0){
writeMsg("error, no waves enabled:");
return;
}
clearPixBuffer();
send_ajax("runDemo");
}
//
// send_ajax (send an HTTP message via AJAX)
//
function send_ajax(action){
switch(state){
case 0:
writeMsg("Connecting");
var msg = ""; // init
switch(action){
case "runDemo":
msg = (nsr_protocol +"//"+ nsr_domain+ "/cgi-bin/fft_demo?op="+action+
"&f1="+f1+"&f2="+f2+"&f3="+f3+
"&a1="+a1+"&a2="+a2+"&a3="+a3+
"&w1="+w1+"&w2="+w2+"&w3="+w3)
console.log("msg:"+ msg);
start_ajax(msg);
break;
case "setDefaults1":
case "setDefaults2":
case "setDefaults3":
msg=nsr_protocol+"//"+nsr_domain+"/cgi-bin/fft_demo?op="+ action;
start_ajax(msg);
break;
default:
writeMsg("programmer error (001)");
break;
}
default:
writeMsg("Please wait up to "+SECS+" seconds (state:"+state+")");
break;
}
}
//
// start_ajax (start an AJAX transaction)
//
function start_ajax(cmd){
response=null;
if (typeof XMLHttpRequest == "undefined")
XMLHttpRequest=function(){
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP") } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP") } catch (e) {}
throw new Error("This browser does not support XMLHttpRequest or XMLHTTP.")
}
if (cmd != ""){
//
// code to block AJAX caching
//
var dt = new Date(); // today's local date time on YOUR machine
var stamp = ""+ dt.getTime(); // millisecs since 1970-01-01
// console.log("time:",stamp);
//
// original code continues
//
response=new XMLHttpRequest();
if (response != null){
cmd += "×tamp=" + stamp; // to foil browser caching
console.log("cmd:",cmd);
response.onreadystatechange=ajax_event_handler;
response.open("GET", cmd, true); // async=true
response.send(null); // not null for POST
}
state = 1;
init_timer1();
}
}
//
// ajax_event_handler (only executed if something is received)
//
function ajax_event_handler(){
if (response.readyState == 4){
state=0;
if (timeOutId1!=0){
clearTimeout(timeOutId1);
timeOutId1=0;
}
if (response.status == 500) {
writeMsg("Server Error 500")
}
if (response.status == 200) {
writeMsg("Ready");
var resp$=response.responseText;
if (debugFlag==1) {
console.log("raw data:"+htmlEncode(resp$));
}
if (window.DOMParser) {
parser=new DOMParser();
gXmlDoc=parser.parseFromString(resp$,"text/xml");
}else{
gXmlDoc=new ActiveXObject("Microsoft.XMLDOM");
gXmlDoc.async=false;
gXmlDoc.loadXML(resp$);
}
var f1 = get_xml_data("f1");
var f2 = get_xml_data("f2");
var f3 = get_xml_data("f3");
var a1 = get_xml_data("a1");
var a2 = get_xml_data("a2");
var a3 = get_xml_data("a3");
var w1 = get_xml_data("w1");
var w2 = get_xml_data("w2");
var w3 = get_xml_data("w3");
var msg = get_xml_data("msg");
var gra = get_xml_data("gra");
var status= get_xml_data("status");
clearPixBuffer();
// var status = 1;
if ((status == 1)||(status == 2)) {
// document.getElementById("msg").innerHTML="raw data:"+htmlEncode(resp$);
if (f1 !=null) document.getElementById("f1").value=f1;
if (f2 !=null) document.getElementById("f2").value=f2;
if (f3 !=null) document.getElementById("f3").value=f3;
if (a1 !=null) document.getElementById("a1").value=a1;
if (a2 !=null) document.getElementById("a2").value=a2;
if (a3 !=null) document.getElementById("a3").value=a3;
if (w1 !=null) changeChecked("w1",w1);
if (w2 !=null) changeChecked("w2",w2);
if (w3 !=null) changeChecked("w3",w3);
if (msg!=null) writeMsg(msg);
if (gra!=null){
if (1==0){
console.log("========== gra is not null ==========");
console.log("len:", gra.length);
console.log("data:", gra);
}
try{
var tmp = gra;
console.log("outputting");
if (1==0){
// this injects properly, but no scripts are run (security restriction by all browsers)
document.getElementById("pix_buffer1").innerHTML = gra;
}else{
// this alternative works properly
// first we create an html object
myDiv = document.getElementById("pix_buffer1");
// now we inject the html string
myDiv.innerHTML = gra;
// now we will parse (and run) the scripts within
const scripts = myDiv.getElementsByTagName('script');
for (let script of scripts) {
const freshScript = document.createElement('script');
if (script.src) {
freshScript.src = script.src;
} else {
freshScript.textContent = script.textContent;
}
document.body.appendChild(freshScript);
}
}
}catch(e){
console.log("error during data extract:",e);
}
}
}else{
writeMsg("error:"+status);
}
}
}
}
function changeChecked(id, state){
console.log("changeChecked1: ",id,state,typeof(state));
if (typeof(state) == "string"){
if (state.toLowerCase() == "true"){
state = true;
}else{
state = false;
}
}
console.log("changeChecked2: ",id,state,typeof(state));
try{
document.getElementById(id).checked=state;
}
catch{
console.log("checked error: ",id,state);
}
}
//
// the server must answer back within 15 seconds
//
function init_timer1(){
if (timeOutId1==0){ // if available
timeOutId1=setTimeout("timer_job1();",MS); // arm timer
}
}
//
// this code is ONLY executed on TIMEOUT
//
function timer_job1(){
state=0; // reset state
if (timeOutId1!=0){
clearTimeout(timeOutId1);
timeOutId1=0;
}
var txt="Error: the timer has expired after "+SECS+" secs";
document.getElementById("msg").innerHTML=txt;
}
//
// does string 'x' represent a positive integer?
//
function isStrInt(x){
x=x.replace(/\s/g,'');
if (x=="") return false;
try{
y = parseInt(x);
}catch(e){
y = -1;
}
if(y>0){
return true;
}else{
return false;
}
}
function htmlEncode(str) {
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
// new way
function htmlDecode(htmlString) {
const doc = new DOMParser().parseFromString(htmlString, 'text/html');
return doc.documentElement.textContent;
}
// old way (IE11, etc.)
function htmlDecode9(text) {
var entities = [
['amp', '&'],
['apos', '\''],
['#x27', '\''],
['#x2F', '/'],
['#39', '\''],
['#47', '/'],
['lt', '<'],
['gt', '>'],
['nbsp', ' '],
['quot', '"']
];
for (var i = 0, max = entities.length; i < max; ++i)
text = text.replace(new RegExp('&' + entities[i][0] + ';', 'g'), entities[i][1]);
return text;
}
//
// get_xml_data (look for one item)
//
function get_xml_data(tag){
var x;
try{
x = gXmlDoc.getElementsByTagName(tag)[0].childNodes[0].nodeValue;
}catch(e){
// console.log("t:"+tag+" e:"+e);
x = null;
}
return(x);
}
//
// get_xml_data2 (look for numerous similar items)
//
function get_xml_data2(xmlObj, tag){
var x;
try{
x = xmlObj.getElementsByTagName(tag)[0].childNodes[0].nodeValue;
}catch(e){
// console.log("t:"+tag+" e:"+e);
x = null;
}
return(x);
}
function convertToJson() {
let form = document.getElementById("dataForm");
let formData = {};
for (let i = 0; i < form.elements.length; i++) {
let element = form.elements[i];
if (element.type !== "submit") {
formData[element.name] = element.value;
}
}
let jsonData = JSON.stringify(formData);
let jsonOutput = document.getElementById("jsonOutput");
jsonOutput.innerHTML = "<pre>" + jsonData + "</pre>";
}
#!/usr/bin/python3.9 # --------------------------------------- # title : /var/www/html/cgi-bin/fft_demo # author : Neil Rieck # created: 2025-05-12 # purpose: ensure use of auto-compiled python (.pyc) # ver who when # 100 NSR 2025-05-12 # 101 NSR 2026-08-28 # --------------------------------------- import fft_demo_101 # invoke JIT auto-compile on this module fft_demo_101.main() # call main (only if it exists) #
#!/usr/bin/python3.9
# ===================================================================
# Title : fft_demo_main_101.py
# Author : Neil Rieck
# created : 2025-05-12
# 1) a server-side program (not for interactive use)
# 2) v100 employs mpld3 to render matplotlib graphs in your browser
# v101 employs pltly to render matplotlib graphs in your browser
# who when what
# 100 NSR 2025-05-12 original effort using mpld3 (AlmaLinux-8)
# 101 NSR 2026-08-19 debug after moving to AlmaLinux-9
# replaced mpld3 with plotly
# replaced html.escape() with markupsafe.escape()
# ===================================================================
#
import math, sys, cgi, cgitb, os, datetime, markupsafe
import numpy as np
from numpy.fft import fft, ifft
#
# constants
#
FNAME = "fft_demo_101"
PATH = "fft-log/"
#
# globals (ugh!)
#
n_samples = 0 # number of samples (init)
ts = 0 # time slots
t = 0 # time data (an array, after processing)
signal = "init" # composite signal data (an array, after processing)
debug = 0 #
dvlp = 0 # 0=off 1=trace 2=data
#
# generate one analog signal
#
def generateSignal(freq=1.0, ampl=1.0):
global n_samples, ts, t
if n_samples == 0: # if first time (if init)
n_samples = 10000 # set number of samples
ts = 1.0/n_samples # sampling interval (time slots)
# note: 1.0 / 1000 = 0.001
# t = np.arange(0, 1, ts) # time
t = np.linspace(0, (n_samples-1)*ts, n_samples)
signal = ampl * np.sin(2*np.pi*freq*t) # generate signal data (a numpy array)
return signal #
#
# text to int (called by processData)
#
def textToInt(data, label, wc):
msg = ""
try:
tmp = int(data)
except Exception:
tmp = 0
if (tmp > 999):
msg = f"Skipped Wave-{wc} because {label} > 999"
if (tmp < 1):
msg = f"Skipped Wave-{wc}. Bad value for {label}"
return tmp, msg
#
# text to real (called by processData)
#
def textToFloat(data, label, wc):
msg = ""
try:
tmp = float(data)
except Exception:
tmp = 0.0
if (tmp > 99.9):
msg = f"Skipped Wave-{wc} because {label} > 99.9"
if tmp < 0.1:
msg = f"Skipped Wave-{wc}. Bad value for {label}"
return tmp, msg
#
# process data (single data set)
# note: this may be called numerous times (or once)
#
def processData(w, f, a, wc, sc, msg9):
global sr, ts, t, signal
wc += 1 # update 'wave count'
if (w == "true"): # if checked
freq, msgF = textToInt(f, "Freq", wc)
if msgF != "":
if msg9 != "":
msg9 += "\n"
msg9 += msgF
ampl, msgA = textToFloat(a, "Ampl", wc)
if msgA != "":
if msg9 != "":
msg9 += "\n"
msg9 += msgA
if (msgF == "") and (msgA == ""): # if both are okay
sc += 1 # update 'signal count'
x = generateSignal(freq, ampl) #
if dvlp >= 2:
logIt(f"{wc} {sc} {type(x)} {len(x)} {x}")
if (type(signal) == str) and (signal == "init"): # if first time
logIt("copy")
signal = x.copy() # copy data as-is
else: # else
logIt("merge")
signal += x.copy() # add the two data items
if dvlp >= 2:
logIt(f"9 9 {type(x)} {len(x)} {x}")
return wc, sc, msg9
#
# generate Graphical Data (only come here when we need to generate a graphic)
#
def generateGraphicalData():
global signal, n_samples, t
logIt("-i-generateGraphicalData()")
#
# call matplotlib etc.
#
os.environ['MPLCONFIGDIR'] = '/tmp'
if debug >= 1:
logIt("trace-1")
import matplotlib.pyplot as plt
#
# developer's recipe:
# 1- 9: mpld3
# 10-10: plotly
#
recipe = 16 # DEVELOPER CHOICE
logIt(f"recipe: {recipe}")
if recipe >= 10: # new way (python3.9 on AlmaLinux-9)
import plotly.graph_objects as go
import plotly.express as px
import plotly.io as pio
else: # old way (python3.9 on AlmaLinux-8)
import mpld3
#
# use: plotly
# notes: this code replaces mpld3 (see below) which stopped working after a server move
#
if recipe == 10: # DVLP: really simple demo (Plotly Express)
fig = px.line(x=[1, 2], y=[3, 4], title="Demo-10")
html_str = fig.to_html(full_html=False, include_plotlyjs=False, div_id="pix_buffer9")
'''
if recipe == 11: # DVLP: a simple demo (Plotly Graph Objects)
fig = go.Figure()
fig.add_trace(go.Scatter(
x=[1, 2, 3, 4, 5],
y=[10, 15, 13, 17, 22],
mode='lines+markers',
name='Sales'
))
fig.update_layout(
title='CGI Generated Line Graph',
xaxis_title='X Axis',
yaxis_title='Y Axis'
)
html_str = pio.to_html(fig, full_html=False, include_plotlyjs=False, div_id="pix_buffer9")
if recipe == 12: # DVLP (no FFT here)
import pandas as pd
# Generate x values from 0 to 4*pi (output to an array)
x = np.linspace(0, 4 * np.pi, 500) # <class 'numpy.ndarray'="">
# Compute 4 different sine waves (output to an array)
y1 = np.sin(x) # <class 'numpy.ndarray'="">
y2 = np.sin(x + np.pi / 4)
y3 = np.sin(2 * x)
y4 = np.sin(2 * x + np.pi / 2)
# Combine 4 wave into a pandas DataFrame
df = pd.DataFrame({
'x': x,
'Sine Wave 1': y1,
'Sine Wave 2 (Phase Shift)': y2,
'Sine Wave 3 (2x Freq)': y3,
'Sine Wave 4 (Shift & Freq)': y4
})
# Pass the DataFrame to Plotly Express for a line plot
fig = px.line(df, x='x', y=['Sine Wave 1', 'Sine Wave 2 (Phase Shift)',
'Sine Wave 3 (2x Freq)', 'Sine Wave 4 (Shift & Freq)'],
title='Demo-12: 4 Different Sine Waves')
html_str = pio.to_html(fig, full_html=False, include_plotlyjs=False, div_id="pix_buffer9")
if recipe == 13: # DVLP (no FFT here)
from plotly.subplots import make_subplots
# Create a figure with 1 row and 2 columns
fig = make_subplots(rows=1, cols=2, subplot_titles=("Plot 1", "Plot 2"))
# Add a scatter plot to the first subplot
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6], mode='lines+markers'), row=1, col=1)
# Add a bar chart to the second subplot
fig.add_trace(go.Bar(x=[1, 2, 3], y=[2, 3, 5]), row=1, col=2)
# Update layout and show
fig.update_layout(title_text="Side-by-Side Subplots Demo", showlegend=False)
html_str = pio.to_html(fig, full_html=False, include_plotlyjs=False, div_id="pix_buffer9")
if recipe == 14: # DVLP (no FFT here)
from plotly.subplots import make_subplots
from plotly import tools
# import plotly.graph_objects as go
#
# Create a figure with 1 row and 2 columns
fig = make_subplots(rows=1, cols=2, subplot_titles=("First Plot", "Second Plot"))
# Add trace to the first subplot (row 1, col 1)
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6], name="Group A"), row=1, col=1)
# Add trace to the second subplot (row 1, col 2)
fig.add_trace(go.Scatter(x=[20, 30, 40], y=[50, 60, 70], name="Group B"), row=1, col=2)
# Update overall layout size and title
fig.update_layout(height=500, width=800, title_text="Integrated Subplots Demo")
html_str = pio.to_html(fig, full_html=False, include_plotlyjs=False, div_id="pix_buffer9")
'''
if recipe == 15: # DVLP (FFT based upon static data)
from numpy import fft
from plotly.subplots import make_subplots
# 1. Generate a noisy composite signal
n_samples = 1000
dt = 0.001
t = np.linspace(0, (n_samples - 1) * dt, n_samples)
# Signal = 50 Hz sine wave + 120 Hz sine wave + random noise
signal = (
5 * np.sin(2 * np.pi * 50 * t)
+ 10 * np.sin(2 * np.pi * 120 * t)
+ np.random.normal(0, 2, n_samples)
)
# 2. Compute FFT
fft_values = np.fft.fft(signal)
fft_freq = np.fft.fftfreq(n_samples, dt)
# Keep only the positive frequencies
pos_mask = fft_freq >= 0
frequencies = fft_freq[pos_mask]
magnitudes = (2 / n_samples) * np.abs(fft_values[pos_mask])
# 3. Create interactive subplots with Plotly
fig = make_subplots(
rows=2, cols=1, subplot_titles=("Time Domain Signal", "Frequency Domain (FFT)")
)
# Time domain trace
fig.add_trace(
go.Scatter(x=t, y=signal, mode="lines", name="Signal", line=dict(color="blue")),
row=1,
col=1,
)
# Frequency domain trace
fig.add_trace(
go.Scatter(
x=frequencies,
y=magnitudes,
mode="lines",
name="FFT Magnitude",
line=dict(color="red"),
),
row=2,
col=1,
)
# Update layout
fig.update_xaxes(title_text="Time (s)", row=1, col=1)
fig.update_yaxes(title_text="Amplitude", row=1, col=1)
fig.update_xaxes(title_text="Frequency (Hz)", row=2, col=1)
fig.update_yaxes(title_text="Magnitude", row=2, col=1)
fig.update_layout(
height=600,
title_text="Interactive FFT Demo with Plotly",
showlegend=False,
)
html_str = pio.to_html(fig, full_html=False, include_plotlyjs=False, div_id="pix_buffer9")
if recipe == 16: # PROD (works like 15 but with variable data)
from numpy import fft
from plotly.subplots import make_subplots
# 1. Generate a noisy composite signal
'''
n_samples = 1000
dt = 0.001 # note: 1.0 seconds / 1000 = 0.001
t = np.linspace(0, (n_samples - 1) * dt, n_samples)
# Signal = 50 Hz sine wave + 120 Hz sine wave + random noise
signal = (
5 * np.sin(2 * np.pi * 50 * t)
+ 10 * np.sin(2 * np.pi * 120 * t)
+ np.random.normal(0, 2, n_samples)
)
'''
dt = 1 # temp fix
# 2. Compute FFT
fft_values = np.fft.fft(signal)
fft_freq = np.fft.fftfreq(n_samples, dt)
# Keep only the positive frequencies
pos_mask = fft_freq >= 0
frequencies = fft_freq[pos_mask]
magnitudes = (2 / n_samples) * np.abs(fft_values[pos_mask])
# 3. Create interactive subplots with Plotly
fig = make_subplots(
rows=2, cols=1, subplot_titles=("Time Domain Signal", "Frequency Domain (FFT)")
)
# Time domain trace
fig.add_trace(
go.Scatter(x=t, y=signal, mode="lines", name="Signal", line=dict(color="blue")),
row=1,
col=1,
)
# Frequency domain trace
fig.add_trace(
go.Scatter(
x=frequencies,
y=magnitudes,
mode="lines",
name="FFT Magnitude",
line=dict(color="red"),
),
row=2,
col=1,
)
# Update layout
fig.update_xaxes(title_text="Time (s)", row=1, col=1)
fig.update_yaxes(title_text="Amplitude", row=1, col=1)
fig.update_xaxes(title_text="Frequency (Hz)", row=2, col=1)
fig.update_yaxes(title_text="Magnitude", row=2, col=1)
fig.update_layout(
height=600,
title_text="Interactive FFT Demo with Plotly",
showlegend=False,
)
html_str = pio.to_html(fig, full_html=False, include_plotlyjs=False, div_id="pix_buffer9")
#
# use: mpld3
# notes: these routines worked properly with mpdl3 (0.5.10) via python3.9 on AlmaLinux-8.
# After the move to AlmaLinux-9, I installed mpdl3 (0.5.12) which is now throwing errors
# with the lastest matplotlib (3.9.4). Rather than attempting to install older libraries,
# I abandonded this code (for now) then switched to plotly.
#
if recipe == 1: # DVLP: really simple demo
fig = plt.figure()
obj = plt.plot([3,1,4,1,5])
html_str = mpld3.fig_to_html(fig, figid='pix_buffer9', include_libraries=False)
if recipe == 2: # DVLP: two plots (one figure)
x = np.array([0, 1, 2, 3])
y = np.array([3, 8, 1, 10])
fig, ax = plt.subplots(1, 2) # create figure and axis objects
ax[0].plot(x,y)
ax[0].set_title('Plot 1')
ax[1].plot(x,y,'r')
ax[1].set_title('Plot 2')
html_str = mpld3.fig_to_html(fig, figid='pix_buffer9', include_libraries=False)
if recipe == 3: # DVLP: simple
fig = plt.figure()
obj = plt.plot(t, x)
html_str = mpld3.fig_to_html(fig, figid='pix_buffer9', include_libraries=False)
if recipe == 4: # DVLP: FFT prep
x9 = np.array([0, 1, 2, 3])
y9 = np.array([3, 8, 1, 10])
fig, ax = plt.subplots(1, 2) # create figure and axis objects
ax[0].plot(t, x)
ax[0].set_title('Signal (time domain)')
ax[1].plot(x9, y9)
ax[1].set_title('FFT (frequenecy domain)')
html_str = mpld3.fig_to_html(fig, figid='pix_buffer9', include_libraries=False)
return html_str #
#
# logIt (a simple logger)
# note: SELinux needs to allow this
#
def logIt(log):
stamp14 = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
try:
stamp8 = stamp14[:8]
fn=f"{PATH}{FNAME}_{stamp8}.txt"
f = open(fn,"a")
f.write(f"-i-event: {stamp14}\n"
f"-i-txt: {log}\n=====\n")
f.close()
except Exception as e:
if debug > 0: # only interactive mode
print(f"-e-error: {e}") #
if debug > 0: #
print(f"-i-txt: {log}") #
#
# the name says it all
#
def main():
global debug
#
# declare/init a few variables
#
w1 = f1 = a1 = "" # wave, freq, amplitude
w2 = f2 = a2 = ""
w3 = f3 = a3 = ""
wc = 0 # wave count
sc = 0 # signal data
#
# declare a few program variables
#
method = os.environ.get('REQUEST_METHOD', "BLANK")
qs = os.environ.get('QUERY_STRING', "BLANK")
#
# a little hook for interactive testing
#
if method == "BLANK":
debug = 1
print("-i-interactive debug mode")
print(f"-i-QUERY_STRING: {qs} (from your shell)")
else:
debug = 0
#
# extract CGI field data
#
fld = cgi.FieldStorage(keep_blank_values=True) # grab all CGI data
a1 = fld.getvalue('a1', '')
a2 = fld.getvalue('a2', '')
a3 = fld.getvalue('a3', '')
f1 = fld.getvalue('f1', '')
f2 = fld.getvalue('f2', '')
f3 = fld.getvalue('f3', '')
w1 = fld.getvalue('w1', '')
w2 = fld.getvalue('w2', '')
w3 = fld.getvalue('w3', '')
op = fld.getvalue('op', 'setDefaults1')
msg = ""
gra = ""
#
log = (f"qs: '{qs}'\n"
f"w1: '{w1}' f1: '{f1}' a1: '{a1}'\n"
f"w2: '{w2}' f2: '{f2}' a2: '{a2}'\n"
f"w3: '{w3}' f3: '{f3}' a3: '{a3}'\n"
f"m : {method}")
logIt(log)
#
# send a response back to Apache
# note: some popular return types include text/html, text/xml, text/plain
#
resp = ('Content-Type: text/xml; charset=windows-1252\n' # we're returning an XML response
'\n' # this marks the end of the HTTP header
'<?xml version="1.0" encoding="iso-8859-1"?>\n' # start of XML content
'\n') # user-defined by me (see JavaScript)
if (op=="setDefaults1"): # user hit button-1
resp += ('1 \n'
'100 9 true \n'
' false \n'
' false \n'
'Program Defaults #1 \n')
elif op=="setDefaults2": # user hit button-2
resp += ('1 \n'
'100 9 true \n'
'50 6 true \n'
' false \n'
'Program Defaults #2 \n')
elif op=="setDefaults3": # user hit button-3
resp += ('1 \n'
'100 9 true \n'
'50 6 true \n'
'25 3 true \n'
'Program Defaults #3 \n')
elif op=="runDemo": # user hit button-4
try:
wc, sc, msg = processData(w1, f1, a1, wc, sc, msg)
wc, sc, msg = processData(w2, f2, a2, wc, sc, msg)
wc, sc, msg = processData(w3, f3, a3, wc, sc, msg)
if msg == "":
msg += "\n"
msg += f"\nSignals generated: {sc}"
if (sc > 0): # if we generated at least one wave
status = 1
gra = generateGraphicalData()
else:
status = 90
log = (f"status: {status}\n"
f"msg: {msg}")
logIt(log)
except Exception as e:
status = 95
msg = f"{e}"
logIt(msg)
#
# since we're sending back XML it might be wise to escape the data
#
# logIt(gra)
# logIt(gra2)
gra2 = markupsafe.escape(gra)
msg2 = markupsafe.escape(msg)
resp += (''+ str(status) +' \n'
f'{msg2} \n'
f'{gra2} \n')
else: # this should never happen
resp = ('1 \n'
'Program Defaults #1 \n')
resp += (' \n')
# logIt(resp)
print(resp) # this goes to Apache
sys.exit() # no more work to do
#
# boilerplate
#
if __name__ == '__main__':
main()
#
# this is the last line
Back to Home