|
| 1 | +""" |
| 2 | +McDermott |
| 3 | +12-20-2024 |
| 4 | +
|
| 5 | +Animate a 1D plot of FDS in-depth profile (CHID_prof_n.csv) |
| 6 | +
|
| 7 | +Usage: Copy this script to your working directory and add your |
| 8 | + profiles to the filenames list. |
| 9 | +
|
| 10 | + Use option --with_slider to control the animation with a time slider |
| 11 | + Use option --save_animation to save the animation (no slider) to a movie file |
| 12 | +
|
| 13 | +Example: |
| 14 | +
|
| 15 | +$ python prof1d.py --with_slider |
| 16 | +
|
| 17 | +""" |
| 18 | + |
| 19 | +import sys |
| 20 | +import pandas as pd |
| 21 | +import matplotlib.pyplot as plt |
| 22 | +import numpy as np |
| 23 | +from matplotlib.animation import FuncAnimation |
| 24 | +from matplotlib.widgets import Slider |
| 25 | +import argparse |
| 26 | + |
| 27 | +parser = argparse.ArgumentParser() |
| 28 | +parser.add_argument('--with_slider', action='store_true', help='Control animation with a time slider') |
| 29 | +parser.add_argument('--save_animation', action='store_true', help='Save animation') |
| 30 | + |
| 31 | +args = parser.parse_args() |
| 32 | + |
| 33 | +# if args.with_slider: |
| 34 | +# print("Flag is present") |
| 35 | +# else: |
| 36 | +# print("Flag is not present") |
| 37 | + |
| 38 | +# sys.exit() |
| 39 | + |
| 40 | +# Close all previously opened figures |
| 41 | +plt.close('all') |
| 42 | + |
| 43 | +scalar_min = [20 , 0, 0, 0, 0, 0] |
| 44 | +scalar_max = [1000, 20, 400, 120, 10, 1] |
| 45 | +scalar_lab = ['T','H2O','PINE','CHAR','ASH','O2'] |
| 46 | + |
| 47 | +filenames = ['../Test/pine_21O2_40_1C_cat_prof_1.csv', |
| 48 | + '../Test/pine_21O2_40_1C_cat_prof_2.csv', |
| 49 | + '../Test/pine_21O2_40_1C_cat_prof_3.csv', |
| 50 | + '../Test/pine_21O2_40_1C_cat_prof_4.csv', |
| 51 | + '../Test/pine_21O2_40_1C_cat_prof_5.csv', |
| 52 | + '../Test/pine_21O2_40_1C_cat_prof_6.csv'] |
| 53 | + |
| 54 | +# create lists to store information about each profile |
| 55 | +IOR = [] |
| 56 | +X = [] |
| 57 | +Y = [] |
| 58 | +Z = [] |
| 59 | +df = {} |
| 60 | + |
| 61 | +for i, filename in enumerate(filenames): |
| 62 | + |
| 63 | + data = [] |
| 64 | + max_cols = 0 |
| 65 | + |
| 66 | + # read header information |
| 67 | + |
| 68 | + with open(filename,'r') as f: |
| 69 | + # Skip the first 1 lines |
| 70 | + for j in range(1): |
| 71 | + next(f) |
| 72 | + first_line = f.readline().strip('\n') |
| 73 | + |
| 74 | + header=first_line.split(",")[1:5] |
| 75 | + IOR.append(int(header[0])) #; print(IOR) |
| 76 | + X.append(float(header[1])) #; print(X) |
| 77 | + Y.append(float(header[2])) #; print(Y) |
| 78 | + Z.append(float(header[3])) #; print(Z) |
| 79 | + next(f) |
| 80 | + |
| 81 | + # Read lines one at a time |
| 82 | + while True: |
| 83 | + line = f.readline() |
| 84 | + if not line: # End of file |
| 85 | + break |
| 86 | + row = line.strip().split(',') # Adjust delimiter if needed |
| 87 | + |
| 88 | + # Convert each element to float, handling errors gracefully |
| 89 | + try: |
| 90 | + row = [float(value) if value else None for value in row] |
| 91 | + except ValueError: |
| 92 | + # If a value cannot be converted, keep it as None |
| 93 | + row = [float(value) if value.replace('.', '', 1).isdigit() else None for value in row] |
| 94 | + |
| 95 | + data.append(row) |
| 96 | + max_cols = max(max_cols, len(row)) # Track the maximum number of columns |
| 97 | + |
| 98 | + # Normalize rows to have the same number of columns |
| 99 | + data = [row + [None] * (max_cols - len(row)) for row in data] |
| 100 | + |
| 101 | + # Convert to a Pandas DataFrame |
| 102 | + df[i] = pd.DataFrame(data) |
| 103 | + |
| 104 | +# sys.exit() |
| 105 | + |
| 106 | +# take time values from first profile |
| 107 | + |
| 108 | +t = df[0].values[:,0] |
| 109 | +time_diff = np.diff(t) # Calculate time difference between consecutive frames |
| 110 | + |
| 111 | +n_points = len(t) |
| 112 | + |
| 113 | +fig, ax = plt.subplots(3, 2, figsize=(12, 8)) |
| 114 | +ax = ax.flatten() # Flatten the array for easier indexing |
| 115 | + |
| 116 | +# Initialize the figure and axis |
| 117 | +# fig, ax = plt.subplots() |
| 118 | +for i in range(len(filenames)): |
| 119 | + line, = ax[i].plot([], [], marker='o', label="Scalar vs X") |
| 120 | + |
| 121 | +time_text = fig.text(0.5, 0.95, "", ha="center", va="center", fontsize=14, weight="bold") |
| 122 | + |
| 123 | +if args.with_slider: |
| 124 | + # Create a slider for controlling time |
| 125 | + axslider = plt.axes([0.1, 0.02, 0.8, 0.03]) |
| 126 | + time_slider = Slider(axslider, 'Time', t[0], t[-1], valinit=t[0]) |
| 127 | + |
| 128 | +def update(frame): |
| 129 | + |
| 130 | + for i in range(len(filenames)): |
| 131 | + n = int(df[i].values[frame,1]) |
| 132 | + x = df[i].values[frame,2:2+n]*100. # in-depth positions (cm) |
| 133 | + scalar_values = df[i].values[frame,2+n:2+2*n] |
| 134 | + |
| 135 | + # Clear the previous plot |
| 136 | + ax[i].cla() |
| 137 | + |
| 138 | + # Plot the scatter plot for the current frame |
| 139 | + ax[i].plot(x, scalar_values, marker='o') |
| 140 | + if scalar_lab[i]=='O2' and np.max(scalar_values)>scalar_min[i]: |
| 141 | + ax[i].set_ylim(scalar_min[i], np.max(scalar_values)*1.1) |
| 142 | + else: |
| 143 | + ax[i].set_ylim(scalar_min[i], scalar_max[i]) |
| 144 | + |
| 145 | + # Set appropriate labels |
| 146 | + ax[i].set_ylabel(scalar_lab[i]) |
| 147 | + |
| 148 | + # Only show X-axis labels and ticks on the bottom row |
| 149 | + if i >= len(filenames) - 2: # Adjust based on layout (3 rows, 2 columns) |
| 150 | + ax[i].set_xlabel('x (cm)') |
| 151 | + else: |
| 152 | + ax[i].set_xticklabels([]) # Remove labels |
| 153 | + |
| 154 | + # Update the shared time text |
| 155 | + time_text.set_text('Time: {:.2f}'.format(t[frame])) |
| 156 | + |
| 157 | +if args.with_slider: |
| 158 | + # Function to update the plot when the slider is moved |
| 159 | + def update_slider(val): |
| 160 | + time = time_slider.val |
| 161 | + index = np.abs(t - time).argmin() # Find the index closest to the current time |
| 162 | + update(index) |
| 163 | + fig.canvas.draw_idle() # Redraw the plot |
| 164 | + |
| 165 | + # Connect the slider to the update_slider function |
| 166 | + time_slider.on_changed(update_slider) |
| 167 | +else: |
| 168 | + # Create the animation |
| 169 | + interval = 200 # milliseconds |
| 170 | + ani = FuncAnimation(fig, update, frames=n_points, interval=interval, blit=False, repeat=False) |
| 171 | + |
| 172 | + if args.save_animation: |
| 173 | + # Save the animation as an mp4 file |
| 174 | + ani.save('animation.mp4', writer='ffmpeg') |
| 175 | + |
| 176 | +plt.show() |
| 177 | + |
| 178 | + |
| 179 | + |
| 180 | + |
0 commit comments