Skip to content

Commit 2abb398

Browse files
committed
Refactor CMTS upstream OFDMA pre equalization visual to ChannelEstCoeff pattern
- align ServingGroup Upstream OFDMA PreEqualization Results layout and chart structure with ChannelEstCoeff design - keep upstream pre_equalization_data source mapping while simplifying UI to the common channel and modem card pattern - sync updated visualizer script into PyPNM-CMTS Postman collection - regenerate MkDocs visual preview and docs page for upstream OFDMA pre equalization - 2026-02-28 13:29:28
1 parent 8746146 commit 2abb398

4 files changed

Lines changed: 13 additions & 547 deletions

File tree

docs/visual-previews/PyPNM-CMTS/ServingGroup/Upstream/OFDMA/PreEqualization/Results/basic.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/visual/PyPNM-CMTS/ServingGroup/Upstream/OFDMA/PreEqualization/Results/basic.md

Lines changed: 3 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Preview is best-effort. Some templates may rely on Postman-specific APIs that ar
1616

1717
````html
1818
// Postman Visualizer: PyPNM-CMTS/ServingGroup/Upstream/OFDMA/PreEqualization/Results/basic
19-
// Last Update: 2026-02-26 01:04:54 MST
19+
// Last Update: 2026-02-28 12:04:00 MST
2020

2121
const response = pm.response.json();
2222

@@ -91,127 +91,6 @@ function normalizeGroupDelay(gd) {
9191
return out;
9292
}
9393

94-
function normalizeScatterPoints(complexValues, complexDimension) {
95-
const arr = Array.isArray(complexValues) ? complexValues : [];
96-
if (!arr.length) return [];
97-
98-
const dim = n(complexDimension);
99-
const out = [];
100-
101-
if (Number.isFinite(dim) && dim >= 2) {
102-
const step = Math.round(dim);
103-
for (let i = 0; i + 1 < arr.length; i += step) {
104-
const xi = n(arr[i]);
105-
const yi = n(arr[i + 1]);
106-
if (xi == null || yi == null) continue;
107-
out.push({ x: xi, y: yi });
108-
}
109-
} else if (Array.isArray(arr[0])) {
110-
for (const pair of arr) {
111-
if (!Array.isArray(pair) || pair.length < 2) continue;
112-
const xi = n(pair[0]);
113-
const yi = n(pair[1]);
114-
if (xi == null || yi == null) continue;
115-
out.push({ x: xi, y: yi });
116-
}
117-
} else {
118-
for (let i = 0; i + 1 < arr.length; i += 2) {
119-
const xi = n(arr[i]);
120-
const yi = n(arr[i + 1]);
121-
if (xi == null || yi == null) continue;
122-
out.push({ x: xi, y: yi });
123-
}
124-
}
125-
126-
if (out.length > 8192) {
127-
const stride = Math.ceil(out.length / 8192);
128-
const sampled = [];
129-
for (let i = 0; i < out.length; i += stride) sampled.push(out[i]);
130-
return sampled;
131-
}
132-
return out;
133-
}
134-
135-
function normalizeComplexCoefficients(complexValues, complexDimension) {
136-
const arr = Array.isArray(complexValues) ? complexValues : [];
137-
if (!arr.length) return [];
138-
const out = [];
139-
const dim = n(complexDimension);
140-
141-
if (Number.isFinite(dim) && dim >= 2) {
142-
const step = Math.round(dim);
143-
for (let i = 0; i + 1 < arr.length; i += step) {
144-
const re = n(arr[i]);
145-
const im = n(arr[i + 1]);
146-
if (re == null || im == null) continue;
147-
out.push({ re, im });
148-
}
149-
} else if (Array.isArray(arr[0])) {
150-
for (const pair of arr) {
151-
if (!Array.isArray(pair) || pair.length < 2) continue;
152-
const re = n(pair[0]);
153-
const im = n(pair[1]);
154-
if (re == null || im == null) continue;
155-
out.push({ re, im });
156-
}
157-
} else {
158-
for (let i = 0; i + 1 < arr.length; i += 2) {
159-
const re = n(arr[i]);
160-
const im = n(arr[i + 1]);
161-
if (re == null || im == null) continue;
162-
out.push({ re, im });
163-
}
164-
}
165-
166-
return out;
167-
}
168-
169-
function toDbLinear(mag, floorDb) {
170-
const floorLinear = Math.pow(10, floorDb / 20);
171-
const clipped = Math.max(mag, floorLinear);
172-
return 20 * Math.log10(clipped);
173-
}
174-
175-
function buildOfdmaAnalyzerSeries(analysis, carrierValues, floorDb = -120, epsilon = 1e-12) {
176-
const cv = carrierValues || {};
177-
const coeffs = normalizeComplexCoefficients(cv.complex, cv.complex_dimension);
178-
if (!coeffs.length) return { preeq_db_points: [], channel_db_points: [] };
179-
180-
const freqList = Array.isArray(cv.frequency) ? cv.frequency : [];
181-
const spacingHz = n(analysis && analysis.subcarrier_spacing);
182-
const firstIdx = n(analysis && analysis.first_active_subcarrier_index);
183-
const zeroHz = n(analysis && analysis.subcarrier_zero_frequency);
184-
const canBuildAxis = spacingHz != null && firstIdx != null && zeroHz != null;
185-
const startHz = canBuildAxis ? (zeroHz + (firstIdx * spacingHz)) : null;
186-
187-
const preeqDb = [];
188-
const chEstDb = [];
189-
190-
for (let i = 0; i < coeffs.length; i += 1) {
191-
let fHz = n(freqList[i]);
192-
if (fHz == null && startHz != null && spacingHz != null) {
193-
fHz = startHz + (i * spacingHz);
194-
}
195-
if (fHz == null) continue;
196-
197-
const re = coeffs[i].re;
198-
const im = coeffs[i].im;
199-
const magSq = (re * re) + (im * im);
200-
const mag = Math.sqrt(magSq);
201-
const preDb = toDbLinear(mag, floorDb);
202-
203-
let chDb = floorDb;
204-
if (magSq >= epsilon) {
205-
chDb = -preDb;
206-
}
207-
208-
preeqDb.push({ x: fHz / 1e6, y: preDb });
209-
chEstDb.push({ x: fHz / 1e6, y: chDb });
210-
}
211-
212-
return { preeq_db_points: preeqDb, channel_db_points: chEstDb };
213-
}
214-
21594
function buildPayload(r) {
21695
const results = (r && r.results) || {};
21796
const capture = results.capture_details || {};
@@ -275,8 +154,6 @@ function buildPayload(r) {
275154
const endMHz = points[points.length - 1].x;
276155
const stats = computeStats(points.map((p) => p.y));
277156
const gd = normalizeGroupDelay(cv.group_delay);
278-
const scatterPoints = normalizeScatterPoints(cv.complex, cv.complex_dimension);
279-
const analyzerSeries = buildOfdmaAnalyzerSeries(analysis, cv);
280157
const gdStats = gd.length ? computeStats(gd) : null;
281158
const spacingHz = n(analysis.subcarrier_spacing) || 0;
282159
const windowKey = [Math.round(startMHz*1000)/1000, Math.round(endMHz*1000)/1000, Math.round(spacingHz), points.length].join('|');
@@ -299,15 +176,10 @@ function buildPayload(r) {
299176
mag_max: fmtn(stats.max, 2),
300177
range_label: fmtInt(startMHz) + ' - ' + fmtInt(endMHz) + ' MHz',
301178
window_label: 'Bandwidth (' + fmtInt(startMHz) + ' - ' + fmtInt(endMHz) + ' MHz)',
302-
chart_id: 'pre-mag-' + chIndex + '-' + mIndex,
179+
chart_id: 'ce-mag-' + chIndex + '-' + mIndex,
303180
points,
304-
gd_chart_id: 'pre-gd-' + chIndex + '-' + mIndex,
181+
gd_chart_id: 'ce-gd-' + chIndex + '-' + mIndex,
305182
gd_points: gd.length ? gd.map((v, i) => ({ x: i, y: v })) : [],
306-
scatter_chart_id: 'pre-scatter-' + chIndex + '-' + mIndex,
307-
scatter_points: scatterPoints,
308-
analyzer_chart_id: 'pre-analyzer-' + chIndex + '-' + mIndex,
309-
preeq_db_points: analyzerSeries.preeq_db_points,
310-
channel_db_points: analyzerSeries.channel_db_points,
311183
gd_avg: gdStats ? fmtn(gdStats.avg, 4) : 'N/A',
312184
gd_p2p: gdStats && gdStats.min != null && gdStats.max != null ? fmtn(gdStats.max - gdStats.min, 4) : 'N/A',
313185
echo_count: echoes.length,
@@ -453,23 +325,6 @@ const template = `
453325
if(!canvas||!window.Chart||!datasets||!datasets.length) return null;
454326
return new Chart(canvas,{type:'line',data:{datasets},options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'nearest',intersect:false},plugins:{legend:{display:false},title:titleText?{display:true,text:titleText,color:axisText}:{display:false},tooltip:{callbacks:{title:(items)=>items.length?(xTitle==='MHz'?(String(Math.round(items[0].parsed.x))+' MHz'):('Index '+items[0].parsed.x)):'' ,label:(ctx)=>(String(ctx.dataset.label)+': '+ctx.parsed.y.toFixed(2))}}},scales:{x:{type:'linear',title:{display:true,text:xTitle,color:axisText},ticks:{callback:(v)=>xTitle==='MHz'?String(Math.round(v)):String(v),color:axisText},grid:{color:gridColor}},y:{title:{display:true,text:yTitle,color:axisText},ticks:{color:axisText},grid:{color:gridColor}}}}});
455327
}
456-
function createScatterChart(canvas, points, titleText){
457-
if(!canvas||!window.Chart||!Array.isArray(points)||!points.length) return null;
458-
return new Chart(canvas,{
459-
type:'scatter',
460-
data:{datasets:[{label:'Scatter',data:points,parsing:false,showLine:false,pointRadius:1.2,pointHoverRadius:1.8,backgroundColor:'#3b82f6',borderColor:'#3b82f6'}]},
461-
options:{
462-
responsive:true,
463-
maintainAspectRatio:false,
464-
animation:false,
465-
plugins:{legend:{display:false},title:titleText?{display:true,text:titleText}:{display:false}},
466-
scales:{
467-
x:{type:'linear',title:{display:true,text:'I',color:axisText},ticks:{display:false,color:axisText},grid:{display:false,color:gridColor}},
468-
y:{type:'linear',title:{display:true,text:'Q',color:axisText},ticks:{display:false,color:axisText},grid:{display:false,color:gridColor}}
469-
}
470-
}
471-
});
472-
}
473328
serviceGroups.forEach((sg)=>{
474329
const sgCard=document.createElement('section'); sgCard.className='channel-card';
475330
const sgHead=document.createElement('div'); sgHead.className='channel-head';
@@ -506,14 +361,6 @@ const template = `
506361
} else {
507362
const gdEmpty=document.createElement('div'); gdEmpty.className='chart-box small'; gdEmpty.style.display='grid'; gdEmpty.style.placeItems='center'; gdEmpty.style.color='var(--muted)'; gdEmpty.textContent='No Group Delay'; card.appendChild(gdEmpty);
508363
}
509-
if ((row.preeq_db_points && row.preeq_db_points.length) || (row.channel_db_points && row.channel_db_points.length)) {
510-
const anLabel=document.createElement('div'); anLabel.className='chart-label'; anLabel.textContent='PreEq Analyzer';
511-
card.appendChild(anLabel);
512-
const anBox=document.createElement('div'); anBox.className='chart-box small';
513-
const anCanvas=document.createElement('canvas'); anCanvas.id=row.analyzer_chart_id;
514-
anBox.appendChild(anCanvas);
515-
card.appendChild(anBox);
516-
}
517364
grid.appendChild(card);
518365
});
519366
wCard.appendChild(grid);
@@ -535,31 +382,6 @@ const template = `
535382
const gd=document.getElementById(row.gd_chart_id);
536383
createLineChart(gd,[{label:'GD',data:row.gd_points,parsing:false,pointRadius:0,borderWidth:1.4,tension:0,borderColor:palette[i%palette.length]}],null,'Index','Group Delay');
537384
}
538-
if (row && row.scatter_points && row.scatter_points.length) {
539-
const card = cv ? cv.closest('.cm-card') : null;
540-
if (card) {
541-
const scLabel=document.createElement('div'); scLabel.className='chart-label'; scLabel.textContent='Scatter';
542-
card.appendChild(scLabel);
543-
const scBox=document.createElement('div'); scBox.className='chart-box small';
544-
const scCanvas=document.createElement('canvas'); scCanvas.id=row.scatter_chart_id;
545-
scBox.appendChild(scCanvas);
546-
card.appendChild(scBox);
547-
createScatterChart(scCanvas, row.scatter_points, null);
548-
}
549-
}
550-
if (row && (row.preeq_db_points || row.channel_db_points)) {
551-
const anCanvas = document.getElementById(row.analyzer_chart_id);
552-
if (anCanvas) {
553-
const ds = [];
554-
if (Array.isArray(row.preeq_db_points) && row.preeq_db_points.length) {
555-
ds.push({ label: 'Applied Pre-EQ (dB)', data: row.preeq_db_points, parsing:false, pointRadius:0, borderWidth:1.5, tension:0, borderColor:'#2563eb' });
556-
}
557-
if (Array.isArray(row.channel_db_points) && row.channel_db_points.length) {
558-
ds.push({ label: 'Estimated Channel (dB)', data: row.channel_db_points, parsing:false, pointRadius:0, borderWidth:1.5, tension:0, borderColor:'#ef4444' });
559-
}
560-
if (ds.length) createLineChart(anCanvas, ds, null, 'MHz', 'dB');
561-
}
562-
}
563385
});
564386
});
565387
});

0 commit comments

Comments
 (0)