-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathplotting_utils.py
More file actions
424 lines (378 loc) · 18.3 KB
/
plotting_utils.py
File metadata and controls
424 lines (378 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
"""
plotting_utils.py
Module for plotting S-parameter curves, metrics, and embedding figures in Excel.
"""
from typing import Optional, List
import os
import matplotlib.pyplot as plt
import xlsxwriter
import numpy as np
def plot_curves_grid_multi(
y_grid, x, curve_labels, out_dir, grid_titles=None,
xlabel='X', ylabel='Y', img_width=1200, img_height=800,
nrows=None, ncols=None, markers=None, marker_texts=None,
legend_loc='best', tight_rect=[0, 0, 0.75, 1],
marker_vlines=None, marker_box=True
):
"""
Plot a grid of plots, each with multiple curves.
y_grid: 2D list of lists of y-values (shape: nrows x ncols x ncurves)
x: shared x-axis (1D array)
curve_labels: list of curve labels (for legend)
out_dir: directory to save images
grid_titles: 2D list of titles for each subplot (optional)
xlabel, ylabel: axis labels
img_width, img_height: image size in pixels
markers: list of lists of marker x-values for each curve (optional)
marker_texts: 2D list of annotation strings for each subplot (optional)
legend_loc: legend location
tight_rect: rect for tight_layout
marker_vlines: list of x-values for vertical lines (optional)
marker_box: whether to show annotation box
Returns: 2D list of image file paths
"""
os.makedirs(out_dir, exist_ok=True)
nrows = nrows or len(y_grid)
ncols = ncols or len(y_grid[0])
dpi = plt.rcParams.get('figure.dpi', 100)
plot_paths: List[List[Optional[str]]] = [[None for _ in range(ncols)] for _ in range(nrows)]
for row in range(nrows):
for col in range(ncols):
plt.figure(figsize=(img_width/dpi, img_height/dpi))
y_curves = y_grid[row][col]
for i, y in enumerate(y_curves):
plt.plot(x, y, lw=2, label=curve_labels[i])
# Optionally add markers
if markers and markers[row][col] and markers[row][col][i]:
for mx in markers[row][col][i]:
idx = np.argmin(np.abs(x - mx))
plt.plot(x[idx], y[idx], marker='o', markersize=10)
# Optionally add vertical lines
if marker_vlines:
for i, vline in enumerate(marker_vlines):
plt.axvline(x=vline, color=['red','blue','green','orange'][i%4], linestyle='--', lw=1.5)
plt.xlabel(xlabel, fontsize=14)
plt.ylabel(ylabel, fontsize=14)
if grid_titles:
plt.title(grid_titles[row][col], fontsize=16)
plt.grid(True, alpha=0.3)
plt.legend(fontsize=12, loc=legend_loc)
# Optionally add annotation box
if marker_texts and marker_texts[row][col] and marker_box:
plt.gcf().text(1.0, 0.5, marker_texts[row][col], transform=plt.gca().transAxes, fontsize=11,
verticalalignment='center', bbox=dict(boxstyle='round,pad=0.5', fc='wheat', alpha=0.7))
plt.tight_layout(rect=tight_rect)
fig_path = os.path.join(out_dir, f"plot_{row+1}_{col+1}.png")
plt.savefig(fig_path)
plt.close()
plot_paths[row][col] = fig_path
return plot_paths
def plot_s_matrix(s_params, frequencies, out_dir, nports=12, img_width=1200, img_height=800):
"""
Plot S(row, col) for the first nports and save as images in out_dir.
Returns a 2D list of image file paths.
"""
os.makedirs(out_dir, exist_ok=True)
plot_paths: List[List[Optional[str]]] = [[None for _ in range(nports)] for _ in range(nports)]
dpi = plt.rcParams.get('figure.dpi', 100)
for row in range(nports):
for col in range(nports):
plt.figure(figsize=(img_width/dpi, img_height/dpi))
s_curve = s_params[:, row, col]
plt.plot(frequencies / 1e9, 20 * np.log10(np.abs(s_curve)), lw=2)
plt.xlabel('Freq (GHz)', fontsize=14)
plt.ylabel(f'S{row+1},{col+1} (dB)', fontsize=14)
plt.title(f'S{row+1},{col+1}', fontsize=16)
plt.grid(True, alpha=0.3)
plt.tight_layout()
fig_path = os.path.join(out_dir, f"S{row+1}_{col+1}.png")
plt.savefig(fig_path)
plt.close()
plot_paths[row][col] = fig_path
return plot_paths
def plot_s_matrix_multi(s_params_dict, frequencies, model_names, out_dir, nports=12, img_width=1200, img_height=800, harmonic_freq_ghz=None):
"""
For each S(row, col), plot all models' curves together with legends, save as images in out_dir.
If harmonic_freq_ghz is provided, add vertical line markers at 1st and 3rd harmonics and annotate dB values in groups.
s_params_dict: dict of {model_name: s_params}
Returns a 2D list of image file paths.
"""
os.makedirs(out_dir, exist_ok=True)
dpi = plt.rcParams.get('figure.dpi', 100)
# Prepare y_grid: nports x nports x nmodels
y_grid = [[[] for _ in range(nports)] for _ in range(nports)]
marker_vlines = None
if harmonic_freq_ghz is not None:
marker_vlines = [harmonic_freq_ghz, 3*harmonic_freq_ghz]
for row in range(nports):
for col in range(nports):
for model_name in model_names:
s_curve = s_params_dict[model_name][:, row, col]
y_db = 20 * np.log10(np.abs(s_curve))
y_grid[row][col].append(y_db)
x = frequencies / 1e9
grid_titles = [[f'S{row+1},{col+1}' for col in range(nports)] for row in range(nports)]
# Generate marker texts using the existing function
marker_texts = None
if harmonic_freq_ghz is not None:
marker_texts = generate_harmonic_marker_texts(y_grid, x, model_names, harmonic_freq_ghz, nports, nports)
return plot_curves_grid_multi(
y_grid, x, model_names, out_dir, grid_titles=grid_titles,
xlabel='Freq (GHz)', ylabel='S(row,col) (dB)', img_width=img_width, img_height=img_height,
nrows=nports, ncols=nports, marker_texts=marker_texts, marker_vlines=marker_vlines,
legend_loc='best', tight_rect=[0, 0, 0.75, 1]
)
def create_excel_with_s_matrix(excel_file, plot_paths, nports=12, img_width=1200, img_height=800):
"""
Create an Excel file with a sheet containing a grid of S-matrix images.
Uses column width = img_width/7.5 and row height = img_height*0.75 for good appearance.
"""
workbook = xlsxwriter.Workbook(excel_file)
worksheet = workbook.add_worksheet("s-matrix")
col_width = (img_width / 7.5) * 1.08
row_height = img_height * 0.75
for col in range(nports):
worksheet.set_column(col, col, col_width)
for row in range(nports):
worksheet.set_row(row, row_height)
for row in range(nports):
for col in range(nports):
cell = xlsxwriter.utility.xl_rowcol_to_cell(row, col) # type: ignore
worksheet.insert_image(cell, plot_paths[row][col],
{'x_scale': 1, 'y_scale': 1, 'object_position': 1})
worksheet2 = workbook.add_worksheet("metrics of interest")
worksheet2.write(0, 0, "(Plots for metrics of interest will be added here)")
workbook.close()
def generate_harmonic_marker_texts(y_grid, x_data, curve_labels, harmonic_freq_ghz, nrows, ncols):
"""
Generate marker texts for harmonic frequencies.
Args:
y_grid: 2D list of lists of y-values
x_data: frequency axis data
curve_labels: list of curve labels (model names)
harmonic_freq_ghz: fundamental harmonic frequency in GHz
nrows, ncols: grid dimensions
Returns:
2D list of marker text strings
"""
marker_texts = [['' for _ in range(ncols)] for _ in range(nrows)]
if harmonic_freq_ghz is None or x_data is None:
return marker_texts
harmonics = [harmonic_freq_ghz, 3 * harmonic_freq_ghz]
for row in range(nrows):
for col in range(ncols):
if y_grid[row][col]:
marker_texts_1st = [f"1st Harmonic ({harmonics[0]:.2f} GHz):"]
marker_texts_3rd = [f"3rd Harmonic ({harmonics[1]:.2f} GHz):"]
for i, model in enumerate(curve_labels):
if i < len(y_grid[row][col]):
y_db = y_grid[row][col][i]
idx1 = np.argmin(np.abs(np.array(x_data) - harmonics[0]))
idx3 = np.argmin(np.abs(np.array(x_data) - harmonics[1]))
marker_texts_1st.append(f" {model}: {y_db[idx1]:.2f} dB")
marker_texts_3rd.append(f" {model}: {y_db[idx3]:.2f} dB")
marker_texts[row][col] = '\n'.join(marker_texts_1st) + '\n\n' + '\n'.join(marker_texts_3rd)
return marker_texts
def generate_metrics_grid_plots(collector, out_dir, img_width=1200, img_height=800, harmonic_freq_ghz=None):
"""
Generate a grid of metrics plots using plot_curves_grid_multi.
Args:
collector: HierarchicalDataCollector with metrics data
out_dir: Directory to save plots
img_width, img_height: Image dimensions
harmonic_freq_ghz: Harmonic frequency for markers
Returns:
Dictionary mapping "metric_signal" to plot file path
"""
os.makedirs(out_dir, exist_ok=True)
data = collector.get()
models = list(data.keys())
metrics = [
"insertion_loss",
"return_loss_left",
"return_loss_right",
"fext",
"psfext",
"next_left",
"psnext_left",
"next_right",
"psnext_right"
]
signals = [f"DQ{i}" for i in range(8)] + ["DQS0-", "DQS0+", "DQS1-", "DQS1+"]
y_grid = [[[] for _ in range(len(signals))] for _ in range(len(metrics))]
x_data = None
for model in models:
for signal in data[model]:
if "frequency_ghz" in data[model][signal]:
x_data = data[model][signal]["frequency_ghz"]
break
if x_data:
break
for metric_idx, metric in enumerate(metrics):
for signal_idx, signal in enumerate(signals):
for model in models:
if signal in data[model] and metric in data[model][signal]:
curve_data = data[model][signal][metric]
if curve_data:
y_grid[metric_idx][signal_idx].append(curve_data)
grid_titles = [[f'{metric.replace("_", " ").title()}\n{signal}' for signal in signals] for metric in metrics]
marker_vlines = [harmonic_freq_ghz, 3*harmonic_freq_ghz] if harmonic_freq_ghz else None
marker_texts = generate_harmonic_marker_texts(y_grid, x_data, models, harmonic_freq_ghz, len(metrics), len(signals))
plot_paths = plot_curves_grid_multi(
y_grid, x_data, models, out_dir, grid_titles=grid_titles,
xlabel='Freq (GHz)', ylabel='Metric (dB)',
img_width=img_width, img_height=img_height,
nrows=len(metrics), ncols=len(signals), marker_texts=marker_texts,
marker_vlines=marker_vlines, legend_loc='best', tight_rect=[0, 0, 0.75, 1]
)
metrics_plot_paths = {}
for metric_idx, metric in enumerate(metrics):
for signal_idx, signal in enumerate(signals):
# Save each plot with a unique filename
plot_key = f"{metric}_{signal}"
fig_path = os.path.join(out_dir, f"{plot_key}.png")
# Copy or rename the plot to the unique filename if needed
orig_path = plot_paths[metric_idx][signal_idx]
if orig_path and orig_path != fig_path:
import shutil
shutil.copyfile(orig_path, fig_path)
if os.path.exists(fig_path):
metrics_plot_paths[plot_key] = fig_path
return metrics_plot_paths
def generate_tdr_grid_plots(tdr_collector, model_names, out_dir, img_width=1200, img_height=800):
"""
Generate TDR plots using plot_curves_grid_multi from HierarchicalDataCollector data.
Args:
tdr_collector: HierarchicalDataCollector with TDR data
model_names: List of model names
out_dir: Directory to save plots
img_width, img_height: Image dimensions
Returns:
Dictionary mapping signal names to plot file paths for left and right sides
"""
os.makedirs(out_dir, exist_ok=True)
# Signal names in order: DQ0-DQ7, DQS0-, DQS0+, DQS1-, DQS1+
signal_names = [f"DQ{i}" for i in range(8)] + ["DQS0-", "DQS0+", "DQS1-", "DQS1+"]
# Prepare data for left side (row 0) and right side (row 1)
y_grid = [[[] for _ in range(len(signal_names))] for _ in range(2)] # 2 rows (left/right), ncols signals
x_data = None
# Get data from collector
collector_data = tdr_collector.get()
for col, signal_name in enumerate(signal_names):
# Get time axis from any model (should be the same for all)
for model_name in model_names:
if model_name in collector_data and signal_name in collector_data[model_name]:
if "time_axis_ns" in collector_data[model_name][signal_name]:
x_data = collector_data[model_name][signal_name]["time_axis_ns"]
break
if x_data:
break
# Extract TDR data for each signal and side
for col, signal_name in enumerate(signal_names):
for model_name in model_names:
if model_name in collector_data and signal_name in collector_data[model_name]:
signal_data = collector_data[model_name][signal_name]
# Left side TDR (row 0)
if "tdr_left" in signal_data:
y_grid[0][col].append(signal_data["tdr_left"])
# Right side TDR (row 1)
if "tdr_right" in signal_data:
y_grid[1][col].append(signal_data["tdr_right"])
# Generate plots using the existing function
grid_titles = [
[f'{signal_name}\n(Left Side)' for signal_name in signal_names], # Row 0: Left side
[f'{signal_name}\n(Right Side)' for signal_name in signal_names] # Row 1: Right side
]
plot_paths = plot_curves_grid_multi(
y_grid, x_data, model_names, out_dir, grid_titles=grid_titles,
xlabel='Time (ns)', ylabel='TDR Magnitude',
img_width=img_width, img_height=img_height,
nrows=2, ncols=len(signal_names), legend_loc='best', tight_rect=[0, 0, 0.75, 1]
)
# Convert to the expected format for Excel
tdr_plot_paths = {}
for col, signal_name in enumerate(signal_names):
# Left side (row 0)
if plot_paths[0][col]:
tdr_plot_paths[f"{signal_name}_left"] = plot_paths[0][col]
# Right side (row 1)
if plot_paths[1][col]:
tdr_plot_paths[f"{signal_name}_right"] = plot_paths[1][col]
return tdr_plot_paths
def create_excel_with_metrics_sheet(excel_file, s_matrix_plot_paths, metrics_plot_paths, tdr_plot_paths=None, nports=12, img_width=1200, img_height=800):
workbook = xlsxwriter.Workbook(excel_file)
# S-matrix sheet
worksheet1 = workbook.add_worksheet("s-matrix")
col_width = (img_width / 7.5) * 1.08
row_height = img_height * 0.75
for col in range(nports):
worksheet1.set_column(col, col, col_width)
for row in range(nports):
worksheet1.set_row(row, row_height)
for row in range(nports):
for col in range(nports):
cell = xlsxwriter.utility.xl_rowcol_to_cell(row, col) # type: ignore
worksheet1.insert_image(cell, s_matrix_plot_paths[row][col],
{'x_scale': 1, 'y_scale': 1, 'object_position': 1})
# Metrics sheet
worksheet2 = workbook.add_worksheet("metrics of interest")
metrics = [
"insertion_loss",
"return_loss_left",
"return_loss_right",
"fext",
"psfext",
"next_left",
"psnext_left",
"next_right",
"psnext_right"
]
signals = [f"DQ{i}" for i in range(8)] + ["DQS0-", "DQS0+", "DQS1-", "DQS1+"]
for col in range(len(signals)):
worksheet2.set_column(col, col, col_width)
for row in range(len(metrics)):
worksheet2.set_row(row, row_height)
if metrics_plot_paths:
for row, metric in enumerate(metrics):
for col, signal in enumerate(signals):
plot_key = f"{metric}_{signal}"
if plot_key in metrics_plot_paths:
cell = xlsxwriter.utility.xl_rowcol_to_cell(row, col) # type: ignore
worksheet2.insert_image(cell, metrics_plot_paths[plot_key],
{'x_scale': 1, 'y_scale': 1, 'object_position': 1})
# TDR sheet
if tdr_plot_paths:
worksheet3 = workbook.add_worksheet("TDR")
signals = [f"DQ{i}" for i in range(8)] + ["DQS0", "DQS1"]
# Set column widths for signals
for col in range(len(signals)):
worksheet3.set_column(col, col, col_width)
# Set row heights for left and right side TDR
worksheet3.set_row(0, row_height) # Left side TDR
worksheet3.set_row(1, row_height) # Right side TDR
# Add TDR plots
for col, signal in enumerate(signals):
# Handle DQS signals (combine + and -)
if signal == "DQS0":
# Use DQS0- for left side, DQS0+ for right side
left_plot_key = "DQS0-_left"
right_plot_key = "DQS0+_right"
elif signal == "DQS1":
# Use DQS1- for left side, DQS1+ for right side
left_plot_key = "DQS1-_left"
right_plot_key = "DQS1+_right"
else:
# DQ signals use left and right side plots
left_plot_key = f"{signal}_left"
right_plot_key = f"{signal}_right"
# Left side TDR (row 0)
if left_plot_key in tdr_plot_paths:
cell = xlsxwriter.utility.xl_rowcol_to_cell(0, col) # type: ignore
worksheet3.insert_image(cell, tdr_plot_paths[left_plot_key],
{'x_scale': 1, 'y_scale': 1, 'object_position': 1})
# Right side TDR (row 1)
if right_plot_key in tdr_plot_paths:
cell = xlsxwriter.utility.xl_rowcol_to_cell(1, col) # type: ignore
worksheet3.insert_image(cell, tdr_plot_paths[right_plot_key],
{'x_scale': 1, 'y_scale': 1, 'object_position': 1})
workbook.close()