added load tsv format feature
This commit is contained in:
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
@ -299,7 +299,7 @@ class BaseProcessor:
|
||||
# --------------------------------------------------------------------- #
|
||||
# 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.
|
||||
|
||||
@ -312,9 +312,13 @@ class BaseProcessor:
|
||||
vna_config:
|
||||
Snapshot of VNA settings (dataclass or pydantic model supported).
|
||||
reference_data:
|
||||
Open air reference sweep data for background subtraction/normalization.
|
||||
Calibrated open air reference sweep data for background subtraction/normalization.
|
||||
reference_info:
|
||||
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
|
||||
-------
|
||||
@ -329,6 +333,8 @@ class BaseProcessor:
|
||||
"vna_config": self._snapshot_vna_config(vna_config),
|
||||
"reference_data": reference_data,
|
||||
"reference_info": reference_info,
|
||||
"raw_reference_data": raw_reference_data,
|
||||
"calibration_standards": calibration_standards,
|
||||
"timestamp": datetime.now().timestamp(),
|
||||
}
|
||||
)
|
||||
@ -615,12 +621,32 @@ class BaseProcessor:
|
||||
sweep_data = entry["sweep_data"]
|
||||
calibrated_data = entry["calibrated_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({
|
||||
"timestamp": float(entry.get("timestamp")) if entry.get("timestamp") is not None else None,
|
||||
"sweep_points": self._points_to_list(getattr(sweep_data, "points", [])),
|
||||
"calibrated_points": self._points_to_list(getattr(calibrated_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")),
|
||||
})
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
{
|
||||
"open_air": true,
|
||||
"axis": "real",
|
||||
"cut": 0.727,
|
||||
"max": 2.3,
|
||||
"open_air": false,
|
||||
"axis": "phase",
|
||||
"cut": 0.279,
|
||||
"max": 4.0,
|
||||
"gain": 0.5,
|
||||
"start_freq": 100.0,
|
||||
"stop_freq": 8800.0,
|
||||
"stop_freq": 8230.0,
|
||||
"clear_history": false,
|
||||
"data_limit": 500
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"y_min": -45,
|
||||
"y_min": -50,
|
||||
"y_max": 40,
|
||||
"autoscale": true,
|
||||
"show_magnitude": true,
|
||||
|
||||
@ -100,7 +100,7 @@ class ProcessorManager:
|
||||
# --------------------------------------------------------------------- #
|
||||
# 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.
|
||||
|
||||
@ -114,7 +114,7 @@ class ProcessorManager:
|
||||
|
||||
for processor_id, processor in processors_items:
|
||||
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:
|
||||
results[processor_id] = result
|
||||
for cb in callbacks:
|
||||
@ -288,9 +288,19 @@ class ProcessorManager:
|
||||
if latest and latest.sweep_number > self._last_processed_sweep:
|
||||
calibrated = self._apply_calibration(latest)
|
||||
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)
|
||||
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
|
||||
|
||||
# Light-duty polling to reduce wakeups
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "asd",
|
||||
"timestamp": "2025-09-30T15:20:21.546972",
|
||||
"preset_filename": "s11_start100_stop8800_points1000_bw1khz.bin",
|
||||
"description": "asd",
|
||||
"metadata": {}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "ыфвф",
|
||||
"timestamp": "2025-09-30T19:05:27.995476",
|
||||
"preset_filename": "s11_start100_stop8800_points1000_bw1khz.bin",
|
||||
"description": "фывфф",
|
||||
"metadata": {}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -178,9 +178,12 @@ export class ChartManager {
|
||||
<button class="chart-card__action" data-action="upload" title="Load History">
|
||||
<span data-icon="upload"></span>
|
||||
</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>
|
||||
</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">
|
||||
<span data-icon="eye-off"></span>
|
||||
</button>
|
||||
@ -220,6 +223,7 @@ export class ChartManager {
|
||||
case 'fullscreen': this.toggleFullscreen(processorId); break;
|
||||
case 'upload': this.uploadHistory(processorId); break;
|
||||
case 'download': this.downloadChart(processorId); break;
|
||||
case 'export-sweeps': this.exportSweeps(processorId); break;
|
||||
case 'hide':
|
||||
this.hideChart(processorId);
|
||||
if (window.vnaDashboard?.ui) window.vnaDashboard.ui.setProcessorEnabled(processorId, false);
|
||||
@ -413,6 +417,187 @@ export class ChartManager {
|
||||
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() {
|
||||
if (this.emptyState) this.emptyState.classList.add('empty-state--hidden');
|
||||
}
|
||||
|
||||
@ -178,6 +178,14 @@ const ICONS = {
|
||||
{ type: 'circle', attrs: { cx: 12, cy: 12, r: 6 } },
|
||||
{ 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' } }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user