added load tsv format feature

This commit is contained in:
ayzen
2025-10-17 18:00:30 +03:00
parent 7dc03a0c5a
commit 3f3fa39cf9
24 changed files with 242 additions and 32215 deletions

View File

@ -1,18 +0,0 @@
{
"preset": {
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"mode": "s11",
"start_freq": 100000000.0,
"stop_freq": 8800000000.0,
"points": 1000,
"bandwidth": 1000.0
},
"calibration_name": "вфыввф",
"standards": [
"open",
"short",
"load"
],
"created_timestamp": "2025-09-30T18:55:17.618157",
"is_complete": true
}

View File

@ -1,16 +0,0 @@
{
"preset": {
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"mode": "s11",
"start_freq": 100000000.0,
"stop_freq": 8800000000.0,
"points": 1000,
"bandwidth": 1000.0
},
"calibration_name": "вфыввф",
"standard": "load",
"sweep_number": 33,
"sweep_timestamp": 1759247715.1613321,
"created_timestamp": "2025-09-30T18:55:17.618031",
"total_points": 1000
}

View File

@ -1,16 +0,0 @@
{
"preset": {
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"mode": "s11",
"start_freq": 100000000.0,
"stop_freq": 8800000000.0,
"points": 1000,
"bandwidth": 1000.0
},
"calibration_name": "вфыввф",
"standard": "open",
"sweep_number": 18,
"sweep_timestamp": 1759247683.8793015,
"created_timestamp": "2025-09-30T18:55:17.612864",
"total_points": 1000
}

View File

@ -1,16 +0,0 @@
{
"preset": {
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"mode": "s11",
"start_freq": 100000000.0,
"stop_freq": 8800000000.0,
"points": 1000,
"bandwidth": 1000.0
},
"calibration_name": "вфыввф",
"standard": "short",
"sweep_number": 19,
"sweep_timestamp": 1759247685.9623504,
"created_timestamp": "2025-09-30T18:55:17.615536",
"total_points": 1000
}

View File

@ -1,18 +0,0 @@
{
"preset": {
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"mode": "s11",
"start_freq": 100000000.0,
"stop_freq": 8800000000.0,
"points": 1000,
"bandwidth": 1000.0
},
"calibration_name": "еуыеуые",
"standards": [
"open",
"load",
"short"
],
"created_timestamp": "2025-09-26T17:19:50.019248",
"is_complete": true
}

View File

@ -1,16 +0,0 @@
{
"preset": {
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"mode": "s11",
"start_freq": 100000000.0,
"stop_freq": 8800000000.0,
"points": 1000,
"bandwidth": 1000.0
},
"calibration_name": "еуыеуые",
"standard": "load",
"sweep_number": 12,
"sweep_timestamp": 1758896376.33808,
"created_timestamp": "2025-09-26T17:19:50.017201",
"total_points": 1000
}

View File

@ -1,16 +0,0 @@
{
"preset": {
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"mode": "s11",
"start_freq": 100000000.0,
"stop_freq": 8800000000.0,
"points": 1000,
"bandwidth": 1000.0
},
"calibration_name": "еуыеуые",
"standard": "open",
"sweep_number": 10,
"sweep_timestamp": 1758896372.20023,
"created_timestamp": "2025-09-26T17:19:50.015286",
"total_points": 1000
}

View File

@ -1,16 +0,0 @@
{
"preset": {
"filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"mode": "s11",
"start_freq": 100000000.0,
"stop_freq": 8800000000.0,
"points": 1000,
"bandwidth": 1000.0
},
"calibration_name": "еуыеуые",
"standard": "short",
"sweep_number": 13,
"sweep_timestamp": 1758896378.4093437,
"created_timestamp": "2025-09-26T17:19:50.019159",
"total_points": 1000
}

View File

@ -299,7 +299,7 @@ class BaseProcessor:
# --------------------------------------------------------------------- # # --------------------------------------------------------------------- #
# Data path: accept new sweep, recompute, produce result # Data path: accept new sweep, recompute, produce result
# --------------------------------------------------------------------- # # --------------------------------------------------------------------- #
def add_sweep_data(self, sweep_data: Any, calibrated_data: Any, vna_config: ConfigPreset | None, reference_data: Any = None, reference_info: Any = None): def add_sweep_data(self, sweep_data: Any, calibrated_data: Any, vna_config: ConfigPreset | None, reference_data: Any = None, reference_info: Any = None, raw_reference_data: Any = None, calibration_standards: dict | None = None):
""" """
Add the latest sweep to the in-memory history and trigger recalculation. Add the latest sweep to the in-memory history and trigger recalculation.
@ -312,9 +312,13 @@ class BaseProcessor:
vna_config: vna_config:
Snapshot of VNA settings (dataclass or pydantic model supported). Snapshot of VNA settings (dataclass or pydantic model supported).
reference_data: reference_data:
Open air reference sweep data for background subtraction/normalization. Calibrated open air reference sweep data for background subtraction/normalization.
reference_info: reference_info:
ReferenceInfo object with metadata (name, description, etc.) about the reference. ReferenceInfo object with metadata (name, description, etc.) about the reference.
raw_reference_data:
Raw (uncalibrated) reference sweep data for export purposes.
calibration_standards:
Dictionary of calibration standard sweep data (e.g., {"open": SweepData, "short": SweepData, ...}).
Returns Returns
------- -------
@ -329,6 +333,8 @@ class BaseProcessor:
"vna_config": self._snapshot_vna_config(vna_config), "vna_config": self._snapshot_vna_config(vna_config),
"reference_data": reference_data, "reference_data": reference_data,
"reference_info": reference_info, "reference_info": reference_info,
"raw_reference_data": raw_reference_data,
"calibration_standards": calibration_standards,
"timestamp": datetime.now().timestamp(), "timestamp": datetime.now().timestamp(),
} }
) )
@ -615,12 +621,32 @@ class BaseProcessor:
sweep_data = entry["sweep_data"] sweep_data = entry["sweep_data"]
calibrated_data = entry["calibrated_data"] calibrated_data = entry["calibrated_data"]
reference_data = entry.get("reference_data") reference_data = entry.get("reference_data")
raw_reference_data = entry.get("raw_reference_data")
calibration_standards = entry.get("calibration_standards")
reference_info = entry.get("reference_info")
# Export calibration standards if present
cal_standards_export = None
if calibration_standards:
cal_standards_export = {}
for std_name, std_data in calibration_standards.items():
cal_standards_export[std_name] = {
"points": self._points_to_list(getattr(std_data, "points", [])),
"sweep_number": getattr(std_data, "sweep_number", None),
"timestamp": getattr(std_data, "timestamp", None),
}
exported.append({ exported.append({
"timestamp": float(entry.get("timestamp")) if entry.get("timestamp") is not None else None, "timestamp": float(entry.get("timestamp")) if entry.get("timestamp") is not None else None,
"sweep_points": self._points_to_list(getattr(sweep_data, "points", [])), "sweep_points": self._points_to_list(getattr(sweep_data, "points", [])),
"calibrated_points": self._points_to_list(getattr(calibrated_data, "points", [])), "calibrated_points": self._points_to_list(getattr(calibrated_data, "points", [])),
"reference_points": self._points_to_list(getattr(reference_data, "points", [])), "reference_points": self._points_to_list(getattr(reference_data, "points", [])),
"raw_reference_points": self._points_to_list(getattr(raw_reference_data, "points", [])),
"calibration_standards": cal_standards_export,
"reference_info": {
"name": getattr(reference_info, "name", None),
"description": getattr(reference_info, "description", None),
} if reference_info else None,
"vna_config": self._snapshot_vna_config(entry.get("vna_config")), "vna_config": self._snapshot_vna_config(entry.get("vna_config")),
}) })

View File

@ -1,11 +1,11 @@
{ {
"open_air": true, "open_air": false,
"axis": "real", "axis": "phase",
"cut": 0.727, "cut": 0.279,
"max": 2.3, "max": 4.0,
"gain": 0.5, "gain": 0.5,
"start_freq": 100.0, "start_freq": 100.0,
"stop_freq": 8800.0, "stop_freq": 8230.0,
"clear_history": false, "clear_history": false,
"data_limit": 500 "data_limit": 500
} }

View File

@ -1,5 +1,5 @@
{ {
"y_min": -45, "y_min": -50,
"y_max": 40, "y_max": 40,
"autoscale": true, "autoscale": true,
"show_magnitude": true, "show_magnitude": true,

View File

@ -100,7 +100,7 @@ class ProcessorManager:
# --------------------------------------------------------------------- # # --------------------------------------------------------------------- #
# Main processing actions # Main processing actions
# --------------------------------------------------------------------- # # --------------------------------------------------------------------- #
def process_sweep(self, sweep_data: SweepData, calibrated_data: Any, vna_config: ConfigPreset | None, reference_data: Any = None, reference_info: Any = None) -> dict[str, ProcessedResult]: def process_sweep(self, sweep_data: SweepData, calibrated_data: Any, vna_config: ConfigPreset | None, reference_data: Any = None, reference_info: Any = None, raw_reference_data: Any = None, calibration_standards: dict | None = None) -> dict[str, ProcessedResult]:
""" """
Feed a sweep into all processors and dispatch results to callbacks. Feed a sweep into all processors and dispatch results to callbacks.
@ -114,7 +114,7 @@ class ProcessorManager:
for processor_id, processor in processors_items: for processor_id, processor in processors_items:
try: try:
result = processor.add_sweep_data(sweep_data, calibrated_data, vna_config, reference_data, reference_info) result = processor.add_sweep_data(sweep_data, calibrated_data, vna_config, reference_data, reference_info, raw_reference_data, calibration_standards)
if result: if result:
results[processor_id] = result results[processor_id] = result
for cb in callbacks: for cb in callbacks:
@ -288,9 +288,19 @@ class ProcessorManager:
if latest and latest.sweep_number > self._last_processed_sweep: if latest and latest.sweep_number > self._last_processed_sweep:
calibrated = self._apply_calibration(latest) calibrated = self._apply_calibration(latest)
vna_cfg = self.settings_manager.get_current_preset() vna_cfg = self.settings_manager.get_current_preset()
reference_data = self._apply_calibration(self.settings_manager.get_current_reference_sweep(vna_cfg))
# Get raw and calibrated reference data
raw_reference_sweep = self.settings_manager.get_current_reference_sweep(vna_cfg)
calibrated_reference_data = self._apply_calibration(raw_reference_sweep)
reference_info = self.settings_manager.get_current_reference(vna_cfg) reference_info = self.settings_manager.get_current_reference(vna_cfg)
self.process_sweep(latest, calibrated, vna_cfg, reference_data, reference_info)
# Get calibration standards for export
calib_set = self.settings_manager.get_current_calibration()
calibration_standards = None
if calib_set and calib_set.standards:
calibration_standards = {std.value: sweep_data for std, sweep_data in calib_set.standards.items()}
self.process_sweep(latest, calibrated, vna_cfg, calibrated_reference_data, reference_info, raw_reference_sweep, calibration_standards)
self._last_processed_sweep = latest.sweep_number self._last_processed_sweep = latest.sweep_number
# Light-duty polling to reduce wakeups # Light-duty polling to reduce wakeups

View File

@ -1,7 +0,0 @@
{
"name": "asd",
"timestamp": "2025-09-30T15:20:21.546972",
"preset_filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"description": "asd",
"metadata": {}
}

View File

@ -1,7 +0,0 @@
{
"name": "ыфвф",
"timestamp": "2025-09-30T19:05:27.995476",
"preset_filename": "s11_start100_stop8800_points1000_bw1khz.bin",
"description": "фывфф",
"metadata": {}
}

View File

@ -178,9 +178,12 @@ export class ChartManager {
<button class="chart-card__action" data-action="upload" title="Load History"> <button class="chart-card__action" data-action="upload" title="Load History">
<span data-icon="upload"></span> <span data-icon="upload"></span>
</button> </button>
<button class="chart-card__action" data-action="download" title="Download"> <button class="chart-card__action" data-action="download" title="Download JSON">
<span data-icon="download"></span> <span data-icon="download"></span>
</button> </button>
<button class="chart-card__action" data-action="export-sweeps" title="Export Sweeps (TSV)">
<span data-icon="database"></span>
</button>
<button class="chart-card__action" data-action="hide" title="Hide"> <button class="chart-card__action" data-action="hide" title="Hide">
<span data-icon="eye-off"></span> <span data-icon="eye-off"></span>
</button> </button>
@ -220,6 +223,7 @@ export class ChartManager {
case 'fullscreen': this.toggleFullscreen(processorId); break; case 'fullscreen': this.toggleFullscreen(processorId); break;
case 'upload': this.uploadHistory(processorId); break; case 'upload': this.uploadHistory(processorId); break;
case 'download': this.downloadChart(processorId); break; case 'download': this.downloadChart(processorId); break;
case 'export-sweeps': this.exportSweeps(processorId); break;
case 'hide': case 'hide':
this.hideChart(processorId); this.hideChart(processorId);
if (window.vnaDashboard?.ui) window.vnaDashboard.ui.setProcessorEnabled(processorId, false); if (window.vnaDashboard?.ui) window.vnaDashboard.ui.setProcessorEnabled(processorId, false);
@ -413,6 +417,187 @@ export class ChartManager {
await togglePlotlyFullscreen(c.element, c.plotContainer); await togglePlotlyFullscreen(c.element, c.plotContainer);
} }
async exportSweeps(id) {
const c = this.charts.get(id);
if (!c?.plotContainer) return;
try {
// Request full processor state from backend via WebSocket
const websocket = window.vnaDashboard?.websocket;
if (!websocket || !websocket.getProcessorState) {
this.notifications?.show?.({
type: 'error',
title: 'Ошибка экспорта',
message: 'WebSocket не доступен'
});
return;
}
// Set up one-time listener for processor_state response
const stateHandler = async (payload) => {
if (payload.processor_id === id) {
websocket.off('processor_state', stateHandler);
try {
await this.performSweepExport(id, payload);
} catch (e) {
console.error('Sweep export failed:', e);
this.notifications?.show?.({
type: 'error',
title: 'Ошибка экспорта',
message: `Не удалось экспортировать свипы: ${e.message}`
});
}
}
};
websocket.on('processor_state', stateHandler);
websocket.getProcessorState(id);
// Timeout fallback
setTimeout(() => {
websocket.off('processor_state', stateHandler);
}, 10000);
} catch (e) {
console.error('Export sweeps failed:', e);
this.notifications?.show?.({
type: 'error',
title: 'Ошибка экспорта',
message: 'Не удалось экспортировать данные свипов'
});
}
}
async performSweepExport(processorId, statePayload) {
if (!statePayload || !statePayload.state) {
throw new Error('Нет данных состояния процессора');
}
const { state } = statePayload;
const sweepHistory = state.sweep_history || [];
console.log('performSweepExport: sweepHistory length:', sweepHistory.length);
if (sweepHistory.length === 0) {
throw new Error('Нет данных свипов для экспорта');
}
// Get latest sweep (most recent)
const latestSweep = sweepHistory[sweepHistory.length - 1];
console.log('performSweepExport: latestSweep keys:', Object.keys(latestSweep));
console.log('performSweepExport: sweep_points length:', latestSweep.sweep_points?.length);
console.log('performSweepExport: calibrated_points length:', latestSweep.calibrated_points?.length);
console.log('performSweepExport: calibration_standards:', latestSweep.calibration_standards);
console.log('performSweepExport: raw_reference_points length:', latestSweep.raw_reference_points?.length);
console.log('performSweepExport: reference_points length:', latestSweep.reference_points?.length);
// Prepare filename with timestamp and preset info
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const presetMode = latestSweep.vna_config?.mode || 'unknown';
const baseFilename = `${processorId}_${presetMode}_${timestamp}`;
let exportedCount = 0;
// Export raw sweep
if (latestSweep.sweep_points && latestSweep.sweep_points.length > 0) {
console.log('Exporting raw sweep, points:', latestSweep.sweep_points.length);
this.exportPointsToTSV(latestSweep.sweep_points, latestSweep.vna_config, `${baseFilename}_raw`);
exportedCount++;
}
// Export calibrated sweep
if (latestSweep.calibrated_points && latestSweep.calibrated_points.length > 0) {
console.log('Exporting calibrated sweep, points:', latestSweep.calibrated_points.length);
this.exportPointsToTSV(latestSweep.calibrated_points, latestSweep.vna_config, `${baseFilename}_calibrated`);
exportedCount++;
}
// Export calibration standards if present
if (latestSweep.calibration_standards) {
console.log('Exporting calibration standards:', Object.keys(latestSweep.calibration_standards));
for (const [standardName, standardData] of Object.entries(latestSweep.calibration_standards)) {
if (standardData && standardData.points && standardData.points.length > 0) {
this.exportPointsToTSV(standardData.points, latestSweep.vna_config, `${baseFilename}_cal_${standardName}`);
exportedCount++;
}
}
}
// Export raw reference if present
if (latestSweep.raw_reference_points && latestSweep.raw_reference_points.length > 0) {
const refName = latestSweep.reference_info?.name || 'reference';
console.log('Exporting raw reference:', refName);
this.exportPointsToTSV(latestSweep.raw_reference_points, latestSweep.vna_config, `${baseFilename}_ref_${refName.replace(/\s/g, '_')}_raw`);
exportedCount++;
}
// Export calibrated reference if present (for comparison)
if (latestSweep.reference_points && latestSweep.reference_points.length > 0) {
const refName = latestSweep.reference_info?.name || 'reference';
console.log('Exporting calibrated reference:', refName);
this.exportPointsToTSV(latestSweep.reference_points, latestSweep.vna_config, `${baseFilename}_ref_${refName.replace(/\s/g, '_')}_calibrated`);
exportedCount++;
}
console.log('Total exported files:', exportedCount);
this.notifications?.show?.({
type: 'success',
title: 'Экспорт завершён',
message: `Данные свипов экспортированы для ${processorId}`
});
}
exportPointsToTSV(points, vnaConfig, filename) {
console.log('exportPointsToTSV called with filename:', filename);
console.log('exportPointsToTSV points length:', points?.length);
if (!points || points.length === 0) {
console.warn('No points to export');
return;
}
const numPoints = points.length;
console.log('exportPointsToTSV: numPoints =', numPoints);
// Generate frequency array
const startFreq = vnaConfig?.start_freq || 100e6;
const stopFreq = vnaConfig?.stop_freq || 8.8e9;
let frequencies;
if (numPoints === 1) {
frequencies = [startFreq];
} else {
const step = (stopFreq - startFreq) / (numPoints - 1);
frequencies = Array.from({ length: numPoints }, (_, i) => startFreq + i * step);
}
// Build TSV content
let tsv = 'Frequency(Hz)\tReal\tImaginary\n';
for (let i = 0; i < numPoints; i++) {
const point = points[i];
const freq = frequencies[i];
const real = point[0];
const imag = point[1];
tsv += `${freq}\t${real}\t${imag}\n`;
}
console.log('exportPointsToTSV: TSV size =', tsv.length, 'chars');
// Download file
const blob = new Blob([tsv], { type: 'text/tab-separated-values;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${filename}.tsv`;
document.body.appendChild(link);
console.log('exportPointsToTSV: Clicking download link for', filename);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
hideEmptyState() { hideEmptyState() {
if (this.emptyState) this.emptyState.classList.add('empty-state--hidden'); if (this.emptyState) this.emptyState.classList.add('empty-state--hidden');
} }

View File

@ -178,6 +178,14 @@ const ICONS = {
{ type: 'circle', attrs: { cx: 12, cy: 12, r: 6 } }, { type: 'circle', attrs: { cx: 12, cy: 12, r: 6 } },
{ type: 'circle', attrs: { cx: 12, cy: 12, r: 10 } } { type: 'circle', attrs: { cx: 12, cy: 12, r: 10 } }
] ]
},
database: {
viewBox: '0 0 24 24',
elements: [
{ type: 'ellipse', attrs: { cx: 12, cy: 5, rx: 9, ry: 3 } },
{ type: 'path', attrs: { d: 'M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5' } },
{ type: 'path', attrs: { d: 'M3 12c0 1.66 4 3 9 3s9-1.34 9-3' } }
]
} }
}; };