added A scan

This commit is contained in:
ayzen
2025-11-10 13:41:39 +03:00
parent 6b0d1647e6
commit 4dddc46bd8
4 changed files with 82 additions and 111 deletions

View File

@ -35,7 +35,7 @@ VNA_PID = 0x5740 # STM32 Virtual ComPort
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Simulator mode settings # Simulator mode settings
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
USE_SIMULATOR = False # Set to True to use simulator instead of real device USE_SIMULATOR = True # Set to True to use simulator instead of real device
SIMULATOR_SWEEP_FILE = BASE_DIR / "binary_input" / "sweep_example" / "example.json" SIMULATOR_SWEEP_FILE = BASE_DIR / "binary_input" / "sweep_example" / "example.json"
SIMULATOR_NOISE_LEVEL = 100 # Standard deviation of Gaussian noise to add to real and imaginary parts SIMULATOR_NOISE_LEVEL = 100 # Standard deviation of Gaussian noise to add to real and imaginary parts

View File

@ -1,5 +1,5 @@
{ {
"open_air": true, "open_air": false,
"axis": "abs", "axis": "abs",
"cut": 0.244, "cut": 0.244,
"max": 1.0, "max": 1.0,

View File

@ -733,7 +733,7 @@ class BScanProcessor(BaseProcessor):
""" """
Return complete processor state including BScan-specific data. Return complete processor state including BScan-specific data.
Extends base implementation with plot history count. Extends base implementation with plot history count and plot data.
""" """
# Get base state (includes active_reference) # Get base state (includes active_reference)
state = super().get_full_state() state = super().get_full_state()
@ -742,4 +742,17 @@ class BScanProcessor(BaseProcessor):
with self._lock: with self._lock:
state["plot_history_count"] = len(self._plot_history) state["plot_history_count"] = len(self._plot_history)
# Include plot history data for preview functionality
all_time_domain = [record["time_domain_data"] for record in self._plot_history]
all_distance = [record["distance_data"] for record in self._plot_history]
all_sweep_numbers = list(range(1, len(self._plot_history) + 1))
all_timestamps = [record["timestamp"] for record in self._plot_history]
state["plot_data"] = {
"all_time_domain_data": all_time_domain,
"all_distance_data": all_distance,
"all_sweep_numbers": all_sweep_numbers,
"all_timestamps": all_timestamps,
}
return state return state

View File

@ -83,7 +83,7 @@ export class BScanClickHandler {
// Add visual highlight by adding a vertical line shape // Add visual highlight by adding a vertical line shape
this.highlightColumn(plotContainer, columnIndex); this.highlightColumn(plotContainer, columnIndex);
console.log(`Column ${columnIndex} selected. Press 'D' to delete.`); console.log(`Column ${columnIndex} selected. Press 'D' to delete, 'P' to preview depth profile.`);
} }
/** /**
@ -171,10 +171,10 @@ export class BScanClickHandler {
this.showDeleteConfirmation(this.selectedProcessorId, this.selectedColumn); this.showDeleteConfirmation(this.selectedProcessorId, this.selectedColumn);
} }
} else if (event.key === 'p' || event.key === 'P') { } else if (event.key === 'p' || event.key === 'P') {
// Show sweep preview modal // Show column depth profile (A-scan)
if (this.selectedColumn !== null && this.selectedProcessorId !== null) { if (this.selectedColumn !== null && this.selectedProcessorId !== null) {
event.preventDefault(); event.preventDefault();
this.showSweepPreview(this.selectedProcessorId, this.selectedColumn); this.showColumnProfile(this.selectedProcessorId, this.selectedColumn);
} }
} else if (event.key === 'Escape') { } else if (event.key === 'Escape') {
// Clear selection on Escape // Clear selection on Escape
@ -183,7 +183,7 @@ export class BScanClickHandler {
}; };
document.addEventListener('keydown', this.keyboardListener); document.addEventListener('keydown', this.keyboardListener);
console.log('Keyboard shortcuts: D - delete column, P - preview sweep, Escape - deselect'); console.log('Keyboard shortcuts: D - delete column, P - preview column depth profile, Escape - deselect');
} }
/** /**
@ -253,11 +253,11 @@ export class BScanClickHandler {
} }
/** /**
* Show sweep preview modal for selected column * Show column depth profile (A-scan) modal for selected column
* @param {string} processorId - Processor ID * @param {string} processorId - Processor ID
* @param {number} columnIndex - Column index (1-based) * @param {number} columnIndex - Column index (1-based)
*/ */
showSweepPreview(processorId, columnIndex) { showColumnProfile(processorId, columnIndex) {
// Get websocket from global window object if not available in instance // Get websocket from global window object if not available in instance
const websocket = this.websocket || window.vnaDashboard?.websocket; const websocket = this.websocket || window.vnaDashboard?.websocket;
@ -271,13 +271,13 @@ export class BScanClickHandler {
} }
try { try {
// Request processor state to get sweep data // Request processor state to get column data
const message = { const message = {
type: 'get_processor_state', type: 'get_processor_state',
processor_id: processorId processor_id: processorId
}; };
console.log('Requesting processor state for sweep preview:', message); console.log('Requesting processor state for column profile:', message);
// Set up one-time listener for the response // Set up one-time listener for the response
const handleResponse = (event) => { const handleResponse = (event) => {
@ -288,8 +288,8 @@ export class BScanClickHandler {
// Remove this listener // Remove this listener
websocket.ws.removeEventListener('message', handleResponse); websocket.ws.removeEventListener('message', handleResponse);
// Extract sweep data for the selected column // Extract column data from plot history
this.displaySweepModal(response, columnIndex); this.displayColumnModal(response, columnIndex);
} }
} catch (error) { } catch (error) {
console.error('Error parsing processor state response:', error); console.error('Error parsing processor state response:', error);
@ -300,61 +300,65 @@ export class BScanClickHandler {
websocket.ws.send(JSON.stringify(message)); websocket.ws.send(JSON.stringify(message));
} catch (error) { } catch (error) {
console.error('Failed to request sweep preview:', error); console.error('Failed to request column profile:', error);
this.notifications?.show?.({ this.notifications?.show?.({
type: 'error', type: 'error',
title: 'Ошибка предпросмотра', title: 'Ошибка предпросмотра',
message: 'Не удалось загрузить данные свипа' message: 'Не удалось загрузить данные столбца'
}); });
} }
} }
/** /**
* Display sweep data in modal window * Display column depth profile data in modal window
* @param {Object} processorState - Processor state from backend * @param {Object} processorState - Processor state from backend
* @param {number} columnIndex - Column index (1-based, same as sweep number) * @param {number} columnIndex - Column index (1-based)
*/ */
displaySweepModal(processorState, columnIndex) { displayColumnModal(processorState, columnIndex) {
const sweepHistory = processorState.state?.sweep_history || []; const plotData = processorState.state?.plot_data || {};
const config = processorState.state?.config || {}; const config = processorState.state?.config || {};
// Convert 1-based column index to 0-based array index // Get plot history data from plot_data
const sweepIndex = columnIndex - 1; const allTimeData = plotData.all_time_domain_data || [];
const allDistanceData = plotData.all_distance_data || [];
if (sweepIndex < 0 || sweepIndex >= sweepHistory.length) { console.log('Plot data available:', {
columns: allTimeData.length,
requestedColumn: columnIndex,
allTimeDataLength: allTimeData.length,
allDistanceDataLength: allDistanceData.length
});
// Convert 1-based column index to 0-based array index
const columnArrayIndex = columnIndex - 1;
if (columnArrayIndex < 0 || columnArrayIndex >= allTimeData.length) {
this.notifications?.show?.({ this.notifications?.show?.({
type: 'error', type: 'error',
title: 'Ошибка', title: 'Ошибка',
message: `Свип ${columnIndex} не найден в истории` message: `Столбец ${columnIndex} не найден в данных (доступно столбцов: ${allTimeData.length})`
}); });
return; return;
} }
const sweep = sweepHistory[sweepIndex]; const amplitudeData = allTimeData[columnArrayIndex];
const depthData = allDistanceData[columnArrayIndex];
// Use same logic as BScanProcessor: calibrated_data or sweep_data if (!amplitudeData || amplitudeData.length === 0 || !depthData || depthData.length === 0) {
const hasCalibrated = this.hasCalibratedSweepData(sweep);
const dataToProcess = hasCalibrated ? sweep.calibrated_points : (sweep.sweep_points || []);
const referencePoints = sweep.reference_points || [];
const openAirEnabled = config.open_air || false;
if (dataToProcess.length === 0) {
this.notifications?.show?.({ this.notifications?.show?.({
type: 'warning', type: 'warning',
title: 'Нет данных', title: 'Нет данных',
message: `Свип ${columnIndex} не содержит данных для отображения` message: `Столбец ${columnIndex} не содержит данных для отображения`
}); });
return; return;
} }
// Show modal with plot // Show modal with depth profile plot
this.showSweepPlotModal( this.showColumnProfileModal(
columnIndex, columnIndex,
dataToProcess, amplitudeData,
referencePoints, depthData,
openAirEnabled, config
hasCalibrated,
sweep.vna_config
); );
} }
@ -443,81 +447,41 @@ export class BScanClickHandler {
} }
/** /**
* Show modal with sweep plot * Show modal with column depth profile (A-scan) plot
* @param {number} sweepNumber - Sweep number (1-based) * @param {number} columnNumber - Column number (1-based)
* @param {Array} points - Sweep points (calibrated or raw) * @param {Array} amplitudeData - Amplitude values
* @param {Array} referencePoints - Reference points for open air subtraction * @param {Array} depthData - Depth values in meters
* @param {boolean} openAirEnabled - Whether to subtract reference * @param {Object} config - Processor configuration
* @param {boolean} hasCalibrated - Whether the data is calibrated (true) or raw (false)
* @param {Object} vnaConfig - VNA configuration
*/ */
showSweepPlotModal(sweepNumber, points, referencePoints, openAirEnabled, hasCalibrated, vnaConfig) { showColumnProfileModal(columnNumber, amplitudeData, depthData, config) {
// Get modal element // Get modal element
const modal = document.getElementById('plotsModal'); const modal = document.getElementById('plotsModal');
if (!modal) { if (!modal) {
console.error('Plots modal not found'); console.error('Plots modal not found');
return; return;
} }
// Prepare data for Plotly
const frequencies = [];
const magnitudes = [];
const phases = [];
points.forEach((point, index) => { // Create Plotly trace - A-scan format (depth on X-axis, amplitude on Y-axis)
const freq = vnaConfig?.start_freq + (index / (points.length - 1)) * (vnaConfig?.stop_freq - vnaConfig?.start_freq);
frequencies.push(freq / 1e6); // Convert to MHz
let real = point[0] || point.real || point.r || 0;
let imag = point[1] || point.imag || point.i || 0;
// Apply reference subtraction if enabled (same as BScanProcessor)
if (openAirEnabled && referencePoints && referencePoints.length > index) {
const refPoint = referencePoints[index];
const refReal = refPoint[0] || refPoint.real || refPoint.r || 0;
const refImag = refPoint[1] || refPoint.imag || refPoint.i || 0;
// Subtract reference: complex_data - reference_complex
real = real - refReal;
imag = imag - refImag;
}
const magnitude = Math.sqrt(real * real + imag * imag);
const phase = Math.atan2(imag, real) * (180 / Math.PI);
magnitudes.push(magnitude);
phases.push(phase);
});
// Create Plotly traces
const traces = [ const traces = [
{ {
x: frequencies, x: depthData,
y: magnitudes, y: amplitudeData,
type: 'scatter', type: 'scatter',
mode: 'lines', mode: 'lines',
name: 'Амплитуда', name: 'Амплитуда',
line: { color: '#3b82f6', width: 2 } line: { color: '#3b82f6', width: 2 }
},
{
x: frequencies,
y: phases,
type: 'scatter',
mode: 'lines',
name: 'Фаза',
yaxis: 'y2',
line: { color: '#f59e0b', width: 2 }
} }
]; ];
// Build descriptive title // Build descriptive title
const dataType = hasCalibrated ? 'откалиброванные' : 'сырые'; const axisType = config.axis || 'abs';
const refStatus = openAirEnabled && referencePoints && referencePoints.length > 0 ? 'с вычетом референса' : 'без вычета референса'; const refStatus = config.open_air ? 'с вычетом референса' : 'без вычета референса';
const titleText = `Свип ${sweepNumber} (${dataType}, ${refStatus})`; const titleText = `Столбец ${columnNumber} - Профиль глубины (${axisType}, ${refStatus})`;
const layoutOverrides = { const layoutOverrides = {
title: { text: titleText, font: { size: 16, color: '#f1f5f9' } }, title: { text: titleText, font: { size: 16, color: '#f1f5f9' } },
xaxis: { xaxis: {
title: 'Частота (МГц)', title: 'Глубина (м)',
gridcolor: '#334155', gridcolor: '#334155',
zerolinecolor: '#475569', zerolinecolor: '#475569',
color: '#cbd5e1', color: '#cbd5e1',
@ -525,30 +489,19 @@ export class BScanClickHandler {
}, },
yaxis: { yaxis: {
title: 'Амплитуда', title: 'Амплитуда',
side: 'left',
gridcolor: '#334155', gridcolor: '#334155',
zerolinecolor: '#475569', zerolinecolor: '#475569',
color: '#cbd5e1', color: '#cbd5e1',
fixedrange: false fixedrange: false
}, },
yaxis2: { showlegend: false,
title: 'Фаза (°)',
overlaying: 'y',
side: 'right',
gridcolor: '#334155',
zerolinecolor: '#475569',
color: '#cbd5e1',
fixedrange: false
},
showlegend: true,
legend: { x: 0.01, y: 0.99 },
height: 500 height: 500
}; };
const configOverrides = { const configOverrides = {
toImageButtonOptions: { toImageButtonOptions: {
format: 'png', format: 'png',
filename: `sweep-${sweepNumber}-preview`, filename: `column-${columnNumber}-profile`,
height: 600, height: 600,
width: 800, width: 800,
scale: 1 scale: 1
@ -577,7 +530,7 @@ export class BScanClickHandler {
<div class="chart-card__title">${titleText}</div> <div class="chart-card__title">${titleText}</div>
</div> </div>
<div class="chart-card__content"> <div class="chart-card__content">
<div class="chart-card__plot" id="sweep-preview-plot"></div> <div class="chart-card__plot" id="column-profile-plot"></div>
</div> </div>
`; `;
container.appendChild(card); container.appendChild(card);
@ -591,7 +544,7 @@ export class BScanClickHandler {
// Create plot after modal is shown // Create plot after modal is shown
setTimeout(() => { setTimeout(() => {
const plotElement = document.getElementById('sweep-preview-plot'); const plotElement = document.getElementById('column-profile-plot');
if (plotElement) { if (plotElement) {
createPlotlyPlot(plotElement, traces, layoutOverrides, configOverrides); createPlotlyPlot(plotElement, traces, layoutOverrides, configOverrides);
} else { } else {
@ -619,10 +572,15 @@ export class BScanClickHandler {
modal.classList.remove('modal--active'); modal.classList.remove('modal--active');
document.body.style.overflow = ''; document.body.style.overflow = '';
// Cleanup Plotly plot // Cleanup Plotly plot - try both possible plot IDs
const plotElement = document.getElementById('sweep-preview-plot'); const profilePlot = document.getElementById('column-profile-plot');
if (plotElement) { if (profilePlot) {
cleanupPlotly(plotElement); cleanupPlotly(profilePlot);
}
const sweepPlot = document.getElementById('sweep-preview-plot');
if (sweepPlot) {
cleanupPlotly(sweepPlot);
} }
} }