From 4dddc46bd89007ff15d8779c94514044a2f387f4 Mon Sep 17 00:00:00 2001 From: ayzen Date: Mon, 10 Nov 2025 13:41:39 +0300 Subject: [PATCH] added A scan --- vna_system/core/config.py | 2 +- .../core/processors/configs/bscan_config.json | 2 +- .../implementations/bscan_processor.py | 15 +- .../js/modules/charts/bscan-click-handler.js | 174 +++++++----------- 4 files changed, 82 insertions(+), 111 deletions(-) diff --git a/vna_system/core/config.py b/vna_system/core/config.py index f697f44..723b3f7 100644 --- a/vna_system/core/config.py +++ b/vna_system/core/config.py @@ -35,7 +35,7 @@ VNA_PID = 0x5740 # STM32 Virtual ComPort # ----------------------------------------------------------------------------- # 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_NOISE_LEVEL = 100 # Standard deviation of Gaussian noise to add to real and imaginary parts diff --git a/vna_system/core/processors/configs/bscan_config.json b/vna_system/core/processors/configs/bscan_config.json index b585b83..f518223 100644 --- a/vna_system/core/processors/configs/bscan_config.json +++ b/vna_system/core/processors/configs/bscan_config.json @@ -1,5 +1,5 @@ { - "open_air": true, + "open_air": false, "axis": "abs", "cut": 0.244, "max": 1.0, diff --git a/vna_system/core/processors/implementations/bscan_processor.py b/vna_system/core/processors/implementations/bscan_processor.py index 387e0fa..4f4bd5a 100644 --- a/vna_system/core/processors/implementations/bscan_processor.py +++ b/vna_system/core/processors/implementations/bscan_processor.py @@ -733,7 +733,7 @@ class BScanProcessor(BaseProcessor): """ 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) state = super().get_full_state() @@ -742,4 +742,17 @@ class BScanProcessor(BaseProcessor): with self._lock: 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 diff --git a/vna_system/web_ui/static/js/modules/charts/bscan-click-handler.js b/vna_system/web_ui/static/js/modules/charts/bscan-click-handler.js index 65fd1e5..1012c47 100644 --- a/vna_system/web_ui/static/js/modules/charts/bscan-click-handler.js +++ b/vna_system/web_ui/static/js/modules/charts/bscan-click-handler.js @@ -83,7 +83,7 @@ export class BScanClickHandler { // Add visual highlight by adding a vertical line shape 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); } } 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) { event.preventDefault(); - this.showSweepPreview(this.selectedProcessorId, this.selectedColumn); + this.showColumnProfile(this.selectedProcessorId, this.selectedColumn); } } else if (event.key === 'Escape') { // Clear selection on Escape @@ -183,7 +183,7 @@ export class BScanClickHandler { }; 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 {number} columnIndex - Column index (1-based) */ - showSweepPreview(processorId, columnIndex) { + showColumnProfile(processorId, columnIndex) { // Get websocket from global window object if not available in instance const websocket = this.websocket || window.vnaDashboard?.websocket; @@ -271,13 +271,13 @@ export class BScanClickHandler { } try { - // Request processor state to get sweep data + // Request processor state to get column data const message = { type: 'get_processor_state', 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 const handleResponse = (event) => { @@ -288,8 +288,8 @@ export class BScanClickHandler { // Remove this listener websocket.ws.removeEventListener('message', handleResponse); - // Extract sweep data for the selected column - this.displaySweepModal(response, columnIndex); + // Extract column data from plot history + this.displayColumnModal(response, columnIndex); } } catch (error) { console.error('Error parsing processor state response:', error); @@ -300,61 +300,65 @@ export class BScanClickHandler { websocket.ws.send(JSON.stringify(message)); } catch (error) { - console.error('Failed to request sweep preview:', error); + console.error('Failed to request column profile:', error); this.notifications?.show?.({ type: 'error', 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 {number} columnIndex - Column index (1-based, same as sweep number) + * @param {number} columnIndex - Column index (1-based) */ - displaySweepModal(processorState, columnIndex) { - const sweepHistory = processorState.state?.sweep_history || []; + displayColumnModal(processorState, columnIndex) { + const plotData = processorState.state?.plot_data || {}; const config = processorState.state?.config || {}; - // Convert 1-based column index to 0-based array index - const sweepIndex = columnIndex - 1; + // Get plot history data from plot_data + 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?.({ type: 'error', title: 'Ошибка', - message: `Свип ${columnIndex} не найден в истории` + message: `Столбец ${columnIndex} не найден в данных (доступно столбцов: ${allTimeData.length})` }); return; } - const sweep = sweepHistory[sweepIndex]; + const amplitudeData = allTimeData[columnArrayIndex]; + const depthData = allDistanceData[columnArrayIndex]; - // Use same logic as BScanProcessor: calibrated_data or sweep_data - 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) { + if (!amplitudeData || amplitudeData.length === 0 || !depthData || depthData.length === 0) { this.notifications?.show?.({ type: 'warning', title: 'Нет данных', - message: `Свип ${columnIndex} не содержит данных для отображения` + message: `Столбец ${columnIndex} не содержит данных для отображения` }); return; } - // Show modal with plot - this.showSweepPlotModal( + // Show modal with depth profile plot + this.showColumnProfileModal( columnIndex, - dataToProcess, - referencePoints, - openAirEnabled, - hasCalibrated, - sweep.vna_config + amplitudeData, + depthData, + config ); } @@ -443,81 +447,41 @@ export class BScanClickHandler { } /** - * Show modal with sweep plot - * @param {number} sweepNumber - Sweep number (1-based) - * @param {Array} points - Sweep points (calibrated or raw) - * @param {Array} referencePoints - Reference points for open air subtraction - * @param {boolean} openAirEnabled - Whether to subtract reference - * @param {boolean} hasCalibrated - Whether the data is calibrated (true) or raw (false) - * @param {Object} vnaConfig - VNA configuration + * Show modal with column depth profile (A-scan) plot + * @param {number} columnNumber - Column number (1-based) + * @param {Array} amplitudeData - Amplitude values + * @param {Array} depthData - Depth values in meters + * @param {Object} config - Processor configuration */ - showSweepPlotModal(sweepNumber, points, referencePoints, openAirEnabled, hasCalibrated, vnaConfig) { + showColumnProfileModal(columnNumber, amplitudeData, depthData, config) { // Get modal element const modal = document.getElementById('plotsModal'); if (!modal) { console.error('Plots modal not found'); return; } - // Prepare data for Plotly - const frequencies = []; - const magnitudes = []; - const phases = []; - points.forEach((point, index) => { - 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 + // Create Plotly trace - A-scan format (depth on X-axis, amplitude on Y-axis) const traces = [ { - x: frequencies, - y: magnitudes, + x: depthData, + y: amplitudeData, type: 'scatter', mode: 'lines', name: 'Амплитуда', line: { color: '#3b82f6', width: 2 } - }, - { - x: frequencies, - y: phases, - type: 'scatter', - mode: 'lines', - name: 'Фаза', - yaxis: 'y2', - line: { color: '#f59e0b', width: 2 } } ]; // Build descriptive title - const dataType = hasCalibrated ? 'откалиброванные' : 'сырые'; - const refStatus = openAirEnabled && referencePoints && referencePoints.length > 0 ? 'с вычетом референса' : 'без вычета референса'; - const titleText = `Свип ${sweepNumber} (${dataType}, ${refStatus})`; + const axisType = config.axis || 'abs'; + const refStatus = config.open_air ? 'с вычетом референса' : 'без вычета референса'; + const titleText = `Столбец ${columnNumber} - Профиль глубины (${axisType}, ${refStatus})`; const layoutOverrides = { title: { text: titleText, font: { size: 16, color: '#f1f5f9' } }, xaxis: { - title: 'Частота (МГц)', + title: 'Глубина (м)', gridcolor: '#334155', zerolinecolor: '#475569', color: '#cbd5e1', @@ -525,30 +489,19 @@ export class BScanClickHandler { }, yaxis: { title: 'Амплитуда', - side: 'left', gridcolor: '#334155', zerolinecolor: '#475569', color: '#cbd5e1', fixedrange: false }, - yaxis2: { - title: 'Фаза (°)', - overlaying: 'y', - side: 'right', - gridcolor: '#334155', - zerolinecolor: '#475569', - color: '#cbd5e1', - fixedrange: false - }, - showlegend: true, - legend: { x: 0.01, y: 0.99 }, + showlegend: false, height: 500 }; const configOverrides = { toImageButtonOptions: { format: 'png', - filename: `sweep-${sweepNumber}-preview`, + filename: `column-${columnNumber}-profile`, height: 600, width: 800, scale: 1 @@ -577,7 +530,7 @@ export class BScanClickHandler {
${titleText}
-
+
`; container.appendChild(card); @@ -591,7 +544,7 @@ export class BScanClickHandler { // Create plot after modal is shown setTimeout(() => { - const plotElement = document.getElementById('sweep-preview-plot'); + const plotElement = document.getElementById('column-profile-plot'); if (plotElement) { createPlotlyPlot(plotElement, traces, layoutOverrides, configOverrides); } else { @@ -619,10 +572,15 @@ export class BScanClickHandler { modal.classList.remove('modal--active'); document.body.style.overflow = ''; - // Cleanup Plotly plot - const plotElement = document.getElementById('sweep-preview-plot'); - if (plotElement) { - cleanupPlotly(plotElement); + // Cleanup Plotly plot - try both possible plot IDs + const profilePlot = document.getElementById('column-profile-plot'); + if (profilePlot) { + cleanupPlotly(profilePlot); + } + + const sweepPlot = document.getElementById('sweep-preview-plot'); + if (sweepPlot) { + cleanupPlotly(sweepPlot); } }