added A scan
This commit is contained in:
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user