4 Commits

Author SHA1 Message Date
35d5f9188a 6.98 2026-07-27 14:05:13 +08:00
0c01b2127b 6.95 2026-07-27 13:54:41 +08:00
0cbfb764a7 6.97 2026-07-16 15:05:28 +08:00
4ab8863037 6.95 2026-07-13 14:37:51 +08:00
10 changed files with 961 additions and 20 deletions

Binary file not shown.

View File

@ -8,6 +8,11 @@ pub fn interpolate_spline(x: Vec<f64>, y: Vec<f64>, step: f64) ->Vec<(f64, f64)>
spectraltools::interpolate_spline(x, y, step).unwrap()
}
#[tauri::command]
pub fn interpolate_spline_smooth(x: Vec<f64>, y: Vec<f64>, step: f64) ->Vec<(f64, f64)>{
spectraltools::interpolate_spline_smooth(x, y, step).unwrap()
}
#[tauri::command]
pub fn interpolate_spline_at_points(x: Vec<f64>, y: Vec<f64>, x_target: Vec<f64>) -> Vec<f64>{
spectraltools::interpolate_spline_at_points(x, y, x_target).unwrap()

View File

@ -39,6 +39,40 @@ pub fn interpolate_spline<T: Copy + Into<f64>,>(x_t: Vec<T>, y_t: Vec<T>, step:
Ok(result)
}
pub fn interpolate_spline_smooth<T: Copy + Into<f64>>(x_t: Vec<T>, y_t: Vec<T>, step: f64) -> Result<Vec<(f64, f64)>, Box<dyn Error>> {
let x: Vec<f64> = x_t.iter().map(|&x| x.into()).collect();
let y: Vec<f64> = y_t.iter().map(|&y| y.into()).collect();
if x.len() != y.len() {
return Err("x and y must have the same length".into());
}
// 使用 Catmull-Rom 样条插值(平滑曲线)
let keys: Vec<Key<f64, f64>> = x.iter()
.zip(y.iter())
.map(|(&x, &y)| Key::new(x, y, Interpolation::CatmullRom))
.collect();
let spline = Spline::from_vec(keys);
// 计算 x 的最大值和最小值
let &start = x.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
let &end = x.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
// 插值到间隔为 step 的点
let mut result = Vec::new();
let mut t = start;
while t <= end {
if let Some(value) = spline.clamped_sample(t) {
result.push((t, value));
}
t += step;
}
Ok(result)
}
pub fn interpolate_spline_at_points<T: Copy + Into<f64>>(x_t: Vec<T>, y_t: Vec<T>, x_target: Vec<f64>) -> Result<Vec<f64>, Box<dyn Error>> {
let x: Vec<f64> = x_t.iter().map(|&x| x.into()).collect();
let y: Vec<f64> = y_t.iter().map(|&y| y.into()).collect();
@ -181,4 +215,42 @@ fn test_find_peek(){
for p in peaks {
println!("{} {}", p.0, p.1);
}
}
#[test]
fn test_compute_weave_coeff_with_data(){
let pixels = vec![79.0, 128.0, 146.0, 157.0, 201.0, 240.0, 261.0, 334.0, 362.0, 436.0];
let wavelengths = vec![965.779, 892.869, 866.794, 850.887, 785.482, 727.294, 696.543, 587.092, 546.074, 435.833];
let result = compute_weave_coeff(pixels.clone(), wavelengths.clone());
let a0 = result[3]; // 三次项
let a1 = result[2]; // 二次项
let a2 = result[1]; // 一次项
let a3 = result[0]; // 常数项
println!("=== compute_weave_coeff 原始返回 ===");
println!("[{:.15e}, {:.15e}, {:.15e}, {:.15e}]", result[0], result[1], result[2], result[3]);
println!();
println!("=== 前端 bochangxishu ===");
println!("a0 (³项) = {:.15e}", a0);
println!("a1 (²项) = {:.15e}", a1);
println!("a2 (¹项) = {:.15e}", a2);
println!("a3 (常数) = {:.15e}", a3);
println!();
println!("=== 拟合验证 ===");
for i in 0..pixels.len() {
let p = pixels[i];
let pred = result[0] + result[1]*p + result[2]*p.powi(2) + result[3]*p.powi(3);
println!("像素 {:3.0}: 输入={:.3}nm, 拟合={:.3}nm, 误差={:+.3}nm", p, wavelengths[i], pred, pred-wavelengths[i]);
}
println!();
println!("=== 前端公式: weave = a0*p³ + a1*p² + a2*p + a3 ===");
for &p in &[0.0_f64, 79.0, 128.0, 146.0, 157.0, 201.0, 240.0, 261.0, 334.0, 362.0, 436.0, 2047.0] {
let pred = a0*p.powi(3) + a1*p.powi(2) + a2*p + a3;
println!("像素 {:4.0}: {:.3}nm", p, pred);
}
}

View File

@ -258,6 +258,10 @@ pub fn collcect_dark(shuttertime:u32)
thread::spawn(move || {
let mut datasum:Vec<u32>=vec![0;bandsunm];
sensor_set_shutter_open(0);
//延时200ms
thread::sleep(std::time::Duration::from_millis(2000));
for _i in 0..averagenumber {
let data=super::spectralbase::sensor_get_data(shuttertime as i32);
@ -271,6 +275,7 @@ pub fn collcect_dark(shuttertime:u32)
drop(dev_stat); //释放锁
}
sensor_set_shutter_open(1);
thread::sleep(std::time::Duration::from_millis(2000));
let data=datasum.iter().map(|x| *x as f32/averagenumber as f32).collect::<Vec<f32>>();

View File

@ -11,6 +11,7 @@ mod comman1;
mod myformatiris;
use comman1::*;
use algorithm::interpolate_spline;
use algorithm::interpolate_spline_smooth;
use algorithm::sg_smooth;
use mydefine::*;
use iris_spectral::spectralbase::Senortype;
@ -245,6 +246,7 @@ fn main() {
readformport,
sendtoport,
interpolate_spline,
interpolate_spline_smooth,
sg_smooth,
savecalibratefile,
savecalibratefileIRIS,

View File

@ -10,7 +10,7 @@
"package": {
"productName": "SpectralPlot",
"version": "0.6.93"
"version": "0.6.98"
},
"tauri": {

View File

@ -0,0 +1,693 @@
<script>
import SensorMethod from "../SerialPort/SerialportMethod.js";
import * as echarts from "echarts";
import { dialog, fs } from "@tauri-apps/api";
import { invoke } from "@tauri-apps/api/tauri";
export default {
name: "ReflectanceToIris",
data() {
return {
mydev:{sensor_id:"",name:"",serialnumber:""},
option: {},
Devinfo: {
work_mode: 0,
bochangxishu: {
a0: 0,
a1: 0,
a2: 0,
a3: 0
},
bandsum: 0
},
wavelengthSource: "device", // "device", "file"
currentCoeffs: {
a0: 0,
a1: 0,
a2: 0,
a3: 0
},
irisFileWavelengths: [], // 从iris文件导入的波长选项列表
selectedIrisWavelength: null, // 选中的波长数据索引
irisFilePath: "", // iris文件路径
reflectanceData: {
wavelengths: [],
values: [],
fileName: ""
},
resampledData: {
wavelengths: [],
values: []
},
reflectanceFilePath: ""
}
},
mounted() {
// 只初始化图表,不自动加载设备信息
this.$nextTick(() => {
setTimeout(() => {
this.initChart();
setTimeout(() => {
this.echartresize();
// 添加窗口resize监听
window.addEventListener('resize', this.echartresize);
}, 100);
}, 200);
});
},
methods: {
async initDeviceInfo() {
var isdevopen= this.$globalState.isDevOpen;
if(isdevopen==false)
{
alert("设备未打开,请先连接并打开设备");
return;
}
try {
this.Devinfo = await SensorMethod.Get_Device_Info();
// 检查设备是否打开
if (this.Devinfo.error || !this.Devinfo || !this.Devinfo.name || this.Devinfo.name === "暂无") {
alert("设备未打开,请先连接并打开设备");
return;
}
if (this.Devinfo.bochangxishu) {
this.currentCoeffs.a0 = this.Devinfo.bochangxishu.a0 || 0;
this.currentCoeffs.a1 = this.Devinfo.bochangxishu.a1 || 0;
this.currentCoeffs.a2 = this.Devinfo.bochangxishu.a2 || 0;
this.currentCoeffs.a3 = this.Devinfo.bochangxishu.a3 || 0;
}
this.wavelengthSource = 'device';
} catch (err) {
console.error('获取设备信息失败:', err);
alert("获取设备信息失败,请确保设备已连接并打开");
}
},
async importReflectanceFile() {
var options = {
defaultPath: "../",
directory: false,
title: "请选择反射率文件",
filters: [
{ name: "文本文件", extensions: ["txt", "mn"] },
{ name: "所有文件", extensions: ["*"] }
]
}
var pathofdir = await dialog.open(options);
if (!pathofdir) {
return;
}
this.reflectanceFilePath = pathofdir;
try {
const data = await fs.readTextFile(pathofdir);
const lines = data.split('\n');
// 解析第一行获取文件名
const firstLine = lines[0].trim();
let fileName = "";
if (firstLine.startsWith("Wavelength")) {
const parts = firstLine.split('\t');
if (parts.length > 1) {
fileName = parts[1].trim();
}
}
const wavelengths = [];
const values = [];
// 解析数据行(跳过第一行)
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (line !== '') {
const parts = line.split(/\s+/);
if (parts.length >= 2) {
const wavelength = parseFloat(parts[0]);
const value = parseFloat(parts[1]);
if (!isNaN(wavelength) && !isNaN(value)) {
wavelengths.push(wavelength); // 直接使用文件中的波长值
values.push(value);
}
}
}
}
this.reflectanceData = {
wavelengths: wavelengths,
values: values,
fileName: fileName || this.getFileNameFromPath(pathofdir)
};
this.updateChart();
//alert(`成功导入反射率文件,共 ${wavelengths.length} 个数据点`);
} catch (err) {
console.error('读取文件失败:', err);
//alert('读取文件失败: ' + err);
}
},
getFileNameFromPath(path) {
const parts = path.split(/[\\\/]/);
return parts[parts.length - 1].replace(/\.[^/.]+$/, "");
},
async importWavelengthFromIris() {
var options = {
defaultPath: "../",
directory: false,
title: "请选择iris文件",
filters: [
{ name: "IRIS文件", extensions: ["iris"] },
{ name: "所有文件", extensions: ["*"] }
]
}
var pathofdir = await dialog.open(options);
if (!pathofdir) {
return;
}
this.irisFilePath = pathofdir;
try {
// 调用 Rust 函数读取 iris 文件
const irisData = await invoke("getoneirisfile", { path: pathofdir });
// 解析波长信息
const wavelengths = [];
// 从 spectral_info_section 中提取波长系数
if (irisData.spectral_info_section && irisData.spectral_info_section.length > 0) {
for (let i = 0; i < irisData.spectral_info_section.length; i++) {
const info = irisData.spectral_info_section[i];
if (info.info_type === "devinfo" && info.wave_coeff) {
const coeffs = info.wave_coeff;
// 从 spectral_data_section 中查找对应的数据以获取波段数
let bands = 2048; // 默认值
let name = info.sensor_id || `波长数据${i + 1}`;
// 尝试从 spectral_data_section 中找到匹配的数据
if (irisData.spectral_data_section && irisData.spectral_data_section.length > 0) {
for (const spectralData of irisData.spectral_data_section) {
if (spectralData.sensor_id === info.sensor_id) {
bands = spectralData.bands || bands;
name = spectralData.name || name;
break;
}
}
}
// iris文件中的wave_coeff使用a1,a2,a3,a4对应我们的a0,a1,a2,a3
wavelengths.push({
index: i,
name: name,
sensor_id: info.sensor_id || "",
coeffs: {
a0: coeffs.a1 !== undefined ? coeffs.a1 : (coeffs.a0 !== undefined ? coeffs.a0 : 0),
a1: coeffs.a2 !== undefined ? coeffs.a2 : (coeffs.a1 !== undefined ? coeffs.a1 : 0),
a2: coeffs.a3 !== undefined ? coeffs.a3 : (coeffs.a2 !== undefined ? coeffs.a2 : 0),
a3: coeffs.a4 !== undefined ? coeffs.a4 : (coeffs.a3 !== undefined ? coeffs.a3 : 0)
},
bands: bands
});
}
}
}
this.mydev.sensor_id = irisData.spectral_info_section[0].sensor_id;
this.mydev.name = irisData.spectral_info_section[0].name;
this.mydev.serialnumber = irisData.spectral_info_section[0].serialnumber;
// 如果没有从 spectral_info_section 找到,尝试从 spectral_data_section 计算
if (wavelengths.length === 0 && irisData.spectral_data_section && irisData.spectral_data_section.length > 0) {
// 如果 spectral_data_section 有数据但没有波长系数,我们需要提示用户
alert("该iris文件中没有找到波长系数信息请使用设备或手动输入");
return;
}
if (wavelengths.length === 0) {
alert("该iris文件中没有找到波长信息");
return;
}
this.irisFileWavelengths = wavelengths;
this.wavelengthSource = 'file'; // 设置为文件导入模式
// 如果只有一个波长数据,自动选择并更新系数
if (wavelengths.length === 1) {
this.selectedIrisWavelength = 0;
this.updateCoeffsFromFile(0);
} else {
// 多个波长数据,让用户选择(通过下拉框)
this.selectedIrisWavelength = null;
}
//alert(`成功从iris文件导入 ${wavelengths.length} 个波长数据`);
} catch (err) {
console.error('读取iris文件失败:', err);
alert('读取iris文件失败: ' + err);
}
},
updateCoeffsFromFile(index) {
if (this.irisFileWavelengths.length > index) {
const selectedWl = this.irisFileWavelengths[index];
const coeffs = selectedWl.coeffs;
this.currentCoeffs.a0 = coeffs.a0;
this.currentCoeffs.a1 = coeffs.a1;
this.currentCoeffs.a2 = coeffs.a2;
this.currentCoeffs.a3 = coeffs.a3;
// 同步更新 Devinfo从 iris 文件中提取设备信息
// 从 name 或 sensor_id 中提取设备名称和序列号
// name 格式可能是: "name_serialnumber_reflectance_filename" 或 "name_serialnumber"
// sensor_id 格式可能是: "name_serialnumber" 或只有序列号
let deviceName = "Unknown";
let serialNumber = "Unknown";
const nameStr = selectedWl.name || "";
const sensorId = selectedWl.sensor_id || "";
// 优先从 name 中解析(通常包含更多信息)
if (nameStr) {
// name 可能包含 "name_serialnumber_reflectance_filename" 格式
// 或者 "name_serialnumber" 格式
const nameParts = nameStr.split('_');
if (nameParts.length >= 2) {
deviceName = nameParts[0];
// 查找序列号部分(通常在第二个位置,但可能后面还有 "reflectance" 等)
// 假设序列号是第二个部分,或者从第二个到 "reflectance" 之前
let serialIndex = 1;
if (nameParts.length > 2 && nameParts[2].toLowerCase().includes('reflectance')) {
serialNumber = nameParts[1];
} else if (nameParts.length >= 2) {
// 如果只有两部分,第二部分就是序列号
serialNumber = nameParts[1];
}
} else {
deviceName = nameStr;
}
}
// 如果从 name 没有解析到序列号,尝试从 sensor_id 解析
if (serialNumber === "Unknown" && sensorId) {
const sensorParts = sensorId.split('_');
if (sensorParts.length >= 2) {
if (deviceName === "Unknown") {
deviceName = sensorParts[0];
}
serialNumber = sensorParts.slice(1).join('_');
} else {
serialNumber = sensorId;
}
}
// 更新 Devinfo
if (!this.Devinfo) {
this.Devinfo = {};
}
this.Devinfo.name = deviceName;
this.Devinfo.serialnumber = serialNumber;
this.Devinfo.SerialNumber = serialNumber; // 兼容不同的字段名
this.Devinfo.bandsum = selectedWl.bands || (this.resampledData.values.length > 0 ? this.resampledData.values.length : 2048);
if (!this.Devinfo.bochangxishu) {
this.Devinfo.bochangxishu = {};
}
this.Devinfo.bochangxishu.a0 = coeffs.a0;
this.Devinfo.bochangxishu.a1 = coeffs.a1;
this.Devinfo.bochangxishu.a2 = coeffs.a2;
this.Devinfo.bochangxishu.a3 = coeffs.a3;
}
},
async resampleReflectance() {
if (this.reflectanceData.wavelengths.length === 0) {
alert("请先导入反射率文件");
return;
}
// 使用当前系数
const coeffweave1 = this.currentCoeffs.a0;
const coeffweave2 = this.currentCoeffs.a1;
const coeffweave3 = this.currentCoeffs.a2;
const coeffweave4 = this.currentCoeffs.a3;
// 获取波段数
let lenthofdata = 2048; // 默认值
if (this.wavelengthSource === "device") {
lenthofdata = this.Devinfo.bandsum || 2048;
} else if (this.wavelengthSource === "file") {
if (this.selectedIrisWavelength !== null && this.irisFileWavelengths.length > 0) {
lenthofdata = this.irisFileWavelengths[this.selectedIrisWavelength].bands || 2048;
}
} else {
lenthofdata = this.Devinfo.bandsum || 2048;
}
// 计算目标波长
let weavetarget = [];
for (var i = 0; i < lenthofdata; i++) {
var weave = coeffweave1 * i * i * i + coeffweave2 * i * i + coeffweave3 * i + coeffweave4;
weavetarget.push(weave);
}
// 使用插值函数重采样
try {
let valuetarget = await invoke("interpolate_spline_at_points", {
x: this.reflectanceData.wavelengths,
y: this.reflectanceData.values,
xTarget: weavetarget
});
this.resampledData = {
wavelengths: weavetarget,
values: valuetarget
};
this.updateChart();
// alert(`重采样完成,共 ${valuetarget.length} 个数据点`);
} catch (err) {
console.error('重采样失败:', err);
alert('重采样失败: ' + err);
}
},
async saveToIris() {
if (this.resampledData.values.length === 0) {
alert("请先进行重采样");
return;
}
// 构建默认文件名(包含原文件名)
const defaultFileName = (this.reflectanceData.fileName || "reflectance") + ".iris";
// 选择保存路径
var options = {
defaultPath: defaultFileName,
directory: false,
title: "保存iris文件",
filters: [
{ name: "IRIS文件", extensions: ["iris"] }
]
}
var savePath = await dialog.save(options);
if (!savePath) {
return;
}
// 确保文件扩展名为.iris
if (!savePath.endsWith('.iris')) {
savePath += '.iris';
}
// 获取设备信息(如果可用),始终使用当前系数
let devinfo = {};
let deviceName = "Unknown";
if (this.Devinfo && this.Devinfo.name && this.Devinfo.name !== "暂无") {
deviceName = this.Devinfo.name;
}
if (this.Devinfo && this.Devinfo.name && this.Devinfo.name !== "暂无") {
// 使用设备信息,但使用当前系数
devinfo = {
name: "flat_ref",
serialnumber: this.Devinfo.serialnumber || this.Devinfo.SerialNumber || "Unknown",
sensor_id: this.mydev.sensor_id, // sensor_id 使用 name 的值
bands: this.resampledData.values.length,
bochangxishu: {
a0: this.currentCoeffs.a0,
a1: this.currentCoeffs.a1,
a2: this.currentCoeffs.a2,
a3: this.currentCoeffs.a3
}
};
} else {
// 使用当前系数
devinfo = {
name: "flat_ref",
serialnumber: "Unknown",
sensor_id: this.mydev.sensor_id, // sensor_id 使用 name 的值
bands: this.resampledData.values.length,
bochangxishu: {
a0: this.currentCoeffs.a0,
a1: this.currentCoeffs.a1,
a2: this.currentCoeffs.a2,
a3: this.currentCoeffs.a3
}
};
}
// 构建反射率数据对象
const reflectanceDataJson = {
data: this.resampledData.values,
wavelengths: this.resampledData.wavelengths,
fileName: this.reflectanceData.fileName
};
// 调用Rust函数保存
try {
const result = await invoke("save_reflectance_to_iris", {
reflectanceData: reflectanceDataJson,
devinfo: devinfo,
filepath: savePath
});
if (result === "ok") {
alert("成功保存iris文件: " + savePath);
} else {
alert("保存失败: " + result);
}
} catch (err) {
console.error('保存失败:', err);
alert('保存失败: ' + err);
}
},
initChart() {
const chartDom = this.$refs.chart;
if (!chartDom) {
console.error('图表容器未找到');
return;
}
// 如果已经初始化过,先销毁
const existingChart = echarts.getInstanceByDom(chartDom);
if (existingChart) {
existingChart.dispose();
}
let chart = echarts.init(chartDom);
this.option = {
xAxis: {
type: 'value',
boundaryGap: false,
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
}
]
},
animation: false,
yAxis: {
type: 'value'
},
grid: {
left: '5%',
right: '5%',
bottom: '5%',
top: '5%'
},
dataZoom: [
{
type: 'inside',
xAxisIndex: [0],
filterMode: 'none'
},
{
type: 'inside',
yAxisIndex: [0],
filterMode: 'none'
}
],
legend: {
data: ['原始反射率', '重采样后'],
show: true
},
series: [
{
name: '原始反射率',
type: 'line',
data: [],
symbol: 'none',
smooth: false,
step: 'start'
},
{
name: '重采样后',
type: 'line',
data: [],
symbol: 'none',
smooth: false,
step: 'start'
}
]
};
chart.setOption(this.option);
chart.dispatchAction({
type: 'resize'
});
chart.resize();
},
updateChart() {
let chart = echarts.getInstanceByDom(this.$refs.chart);
if (!chart) {
this.initChart();
chart = echarts.getInstanceByDom(this.$refs.chart);
}
// 更新原始数据
const originalData = [];
for (let i = 0; i < this.reflectanceData.wavelengths.length; i++) {
originalData.push([this.reflectanceData.wavelengths[i], this.reflectanceData.values[i]]);
}
this.option.series[0].data = originalData;
// 更新重采样数据
const resampledData = [];
for (let i = 0; i < this.resampledData.wavelengths.length; i++) {
resampledData.push([this.resampledData.wavelengths[i], this.resampledData.values[i]]);
}
this.option.series[1].data = resampledData;
chart.setOption(this.option);
},
echartresize() {
let chart = echarts.getInstanceByDom(this.$refs.chart);
if (chart) {
chart.resize();
}
}
},
beforeUnmount() {
window.removeEventListener('resize', this.echartresize);
}
}
</script>
<template>
<div class="container" style="width: 100%;height: 100%;max-width:100%;margin: 0px;padding: 0px; display: flex; flex-direction: column; overflow: hidden;">
<div class="row" style="flex: 1; min-height: 0; margin: 0px; overflow: hidden;">
<!-- 左侧控制面板 -->
<div class="col-2">
<!-- 标题 -->
<div style="padding: 10px; border-bottom: 1px solid #d2dede; background-color: #e9ecef;">
<h6 style="margin: 0; font-weight: bold; text-align: center;">波长系数</h6>
</div>
<!-- 波长来源选择按钮 -->
<div style="padding: 10px;">
<b-button variant="secondary" @click="initDeviceInfo()"
:variant="wavelengthSource === 'device' ? 'primary' : 'secondary'"
style="width: 100%; margin-bottom: 10px;">
设备导入
</b-button>
<b-button variant="secondary" @click="importWavelengthFromIris()"
:variant="wavelengthSource === 'file' ? 'primary' : 'secondary'"
style="width: 100%; margin-bottom: 10px;">
文件导入
</b-button>
</div>
<!-- 从文件导入时显示选择下拉框 -->
<div v-if="wavelengthSource === 'file' && irisFileWavelengths.length > 0" style="padding: 10px;">
<b-form-select
v-model="selectedIrisWavelength"
:options="irisFileWavelengths.map((wl, index) => ({ value: index, text: wl.name + ' (' + wl.bands + ')' }))"
@change="updateCoeffsFromFile(selectedIrisWavelength)"
style="width: 100%; margin-bottom: 10px;">
<template #first>
<b-form-select-option :value="null" disabled>请选择波长数据</b-form-select-option>
</template>
</b-form-select>
</div>
<!-- 波长系数输入框 - 始终显示并可编辑 -->
<div style="padding: 10px;">
<b-input-group class="my-1">
<BInputGroupPrepend is-text class="myinputprepend">a0</BInputGroupPrepend>
<b-form-input
type="number"
step="0.00000000000001"
v-model.number="currentCoeffs.a0"></b-form-input>
</b-input-group>
<b-input-group class="my-1">
<BInputGroupPrepend is-text class="myinputprepend">a1</BInputGroupPrepend>
<b-form-input
type="number"
step="0.00000000000001"
v-model.number="currentCoeffs.a1"></b-form-input>
</b-input-group>
<b-input-group class="my-1">
<BInputGroupPrepend is-text class="myinputprepend">a2</BInputGroupPrepend>
<b-form-input
type="number"
step="0.00000000000001"
v-model.number="currentCoeffs.a2"></b-form-input>
</b-input-group>
<b-input-group class="my-1">
<BInputGroupPrepend is-text class="myinputprepend">a3</BInputGroupPrepend>
<b-form-input
type="number"
step="0.00000000000001"
v-model.number="currentCoeffs.a3"></b-form-input>
</b-input-group>
</div>
</div>
<!-- 右侧图表区域 -->
<div class="col-10" style="position: relative; overflow: hidden;">
<div class="chart-container" ref="chart" style="width: 100%; height: 100%;"></div>
</div>
</div>
<!-- 底部操作按钮 - 横跨整个界面 -->
<div style="flex-shrink: 0; height: 50px; border-top: 1px solid #d2dede; padding: 10px; display: flex; align-items: center; background-color: #f8f9fa;">
<b-button @click="importReflectanceFile()" style="margin-right: 10px;">打开文件</b-button>
<b-button @click="resampleReflectance()" style="margin-right: 10px;">重采样</b-button>
<b-button variant="primary" @click="saveToIris()" style="margin-left: auto;">保存文件</b-button>
<span v-if="reflectanceData.fileName" style="margin-left: 20px; line-height: 30px;">
当前文件: {{ reflectanceData.fileName }}
</span>
</div>
</div>
</template>
<style scoped>
.col-2 {
background-color: #f8f9fa;
padding-top: 0px;
padding-bottom: 0px;
padding: 0px;
border-right: 1px solid #d2dede;
position: relative;
}
.col-10 {
padding: 0px;
position: relative;
}
.chart-container {
background-color: #f8f9fa;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.container {
background-color: #f8f9fa;
width: 100%;
height: 100%;
max-width: 100%;
}
.myinputprepend {
min-width: 40px;
}
</style>

View File

@ -463,6 +463,7 @@ export default {
let coeffweave3 = this.Devinfo.bochangxishu.a2;
let coeffweave4 = this.Devinfo.bochangxishu.a3;
await SensorMethod.Set_Gain(this.sensor_gain_up);
await SensorMethod.Get_Dark_Data(dire, this.shutter_time_up, Number(this.caijicishu[0]));
var data = await SensorMethod.Get_Date_on_Derction(dire, this.shutter_time_up, true, Number(this.caijicishu[0]))
let lastvalue = this.DataUP.value_lable;
this.DataUP = data;
@ -487,6 +488,7 @@ export default {
let coeffweave3 = this.Devinfo.bochangxishu2.a2;
let coeffweave4 = this.Devinfo.bochangxishu2.a3;
await SensorMethod.Set_Gain(this.sensor_gain_down);
await SensorMethod.Get_Dark_Data(dire, this.shutter_time_up, Number(this.caijicishu[0]));
var data = await SensorMethod.Get_Date_on_Derction(dire, this.shutter_time_down, true, Number(this.caijicishu[1]))
let lastvalue = this.DataDown.value_lable;
this.DataDown = data;

View File

@ -1,5 +1,6 @@
<script>
import {invoke} from "@tauri-apps/api/tauri";
import { dialog, fs } from "@tauri-apps/api";
import * as echarts from "echarts";
import SerialportMethod from "../SerialPort/SerialportMethod.js";
@ -24,11 +25,11 @@ export default {
},
async mounted() {
let that=this;
//await
//await
await setTimeout(function() {
that.GetSenSorInfo();
// 这里是你要在100毫秒后执行的代码
}, 100);
await setTimeout(function() {
that.GetSenSorInfo();
@ -89,7 +90,7 @@ export default {
//min:0,
},
grid: {
left: '5%',
left: 60,
right: '5%',
bottom: '5%',
top: '5%'
@ -156,6 +157,7 @@ export default {
let chartpeak=
{
data:peaksforshow, // 使用二维数组表示数据点的坐标
@ -164,7 +166,7 @@ export default {
show: true,
position: 'top',
formatter: function (params) {
return params.value[2]
return params.value[2]
}
}
@ -205,7 +207,38 @@ export default {
chart_up.setOption(this.option);
// console.log(dataforshow);
},
async Set_Weave_Coeff() {
await SerialportMethod.Set_Weave_Coeff(0, this.Devinfo.bochangxishu.a0, this.Devinfo.bochangxishu.a1, this.Devinfo.bochangxishu.a2, this.Devinfo.bochangxishu.a3);
await this.saveCoeffToFile();
},
async saveCoeffToFile() {
const now = new Date();
const pad = n => String(n).padStart(2, '0');
const dateStr = `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
const serial = this.Devinfo?.serialnumber || this.Devinfo?.SerilNumber || "Unknown";
const defaultName = `${serial}_${dateStr}.json`;
const savePath = await dialog.save({
defaultPath: defaultName,
filters: [{ name: "JSON文件", extensions: ["json"] }]
});
if (!savePath) return;
const data = {
serialnumber: serial,
saveTime: now.toISOString(),
sensor_type: this.Devinfo?.sensor_type || "",
bochangxishu: {
a0: this.Devinfo.bochangxishu.a0,
a1: this.Devinfo.bochangxishu.a1,
a2: this.Devinfo.bochangxishu.a2,
a3: this.Devinfo.bochangxishu.a3,
}
};
await fs.writeTextFile(savePath, JSON.stringify(data, null, 2));
}
}
@ -261,7 +294,7 @@ export default {
</b-input-group>
<b-button @click="GetOneData" style="position:absolute;left: 25% ">获取数据</b-button>
<b-button @click="updataName" style="position:absolute; right: 50% ">计算</b-button>
<b-button @click="updataName" style="position:absolute;right: 20px ">设置</b-button>
<b-button @click="Set_Weave_Coeff" style="position:absolute;right: 20px ">设置</b-button>
</p>
</div>
</div>

View File

@ -1,5 +1,6 @@
<script>
import { invoke } from "@tauri-apps/api/tauri";
import { dialog, fs } from "@tauri-apps/api";
import * as echarts from "echarts";
import SensorMethod from "../SerialPort/SerialportMethod.js";
import { Checkbox } from "@arco-design/web-vue";
@ -27,6 +28,8 @@ export default {
},all_averagetime:1,
sensor1Status: "", // loaded | computed | set
sensor2Status: "", // loaded | computed | set
WeavePeaksDeffine: [
0,
253.652,
@ -103,6 +106,8 @@ export default {
// }
// let message = await invoke("sendtoport_andgetreturn", data);
this.Devinfo = await SensorMethod.Get_Device_Info();
this.sensor1Status = "loaded";
this.sensor2Status = "loaded";
this.sensor_gain_down = this.Devinfo.sensor_gain_down;
if (typeof (this.Devinfo.fiber_type) == "string") {
this.Devinfo.fiber_type = this.Devinfo.fiber_type;
@ -161,7 +166,7 @@ export default {
//min:0,
},
grid: {
left: '5%',
left: 60,
right: '5%',
bottom: '10%',
top: '5%'
@ -271,8 +276,18 @@ export default {
},
async findpeak(specindex) {
if (specindex == 0) {
let dataforpeak = this.Data.data;
var peaks = await invoke("find_peek", { data: dataforpeak, minheigh: 3000 });
let originalData = this.Data.data;
// Step 1: 亚像素插值 (0.05像素间隔20倍数据量)
let resampled = await invoke("interpolate_spline_smooth", {
x: Array.from({length: originalData.length}, (_, i) => i),
y: originalData.map(v => Number(v)),
step: 0.05
});
// Step 2: 在插值数据上寻峰
let resampledY = resampled.map(p => p[1]);
var peaks = await invoke("find_peek", { data: resampledY, minheigh: 3000 });
// Step 3: 峰位置除以20映射回原始像素(亚像素精度)
peaks = peaks.map(p => [p[0] / 20, p[1]]);
console.log(peaks);
let peaksforshow = [];
let coeffweave1 = this.Devinfo.bochangxishu.a0;
@ -294,7 +309,7 @@ export default {
peaks.forEach(element => {
var weave = coeffweave1 * element[0] * element[0] * element[0] + coeffweave2 * element[0] * element[0] + coeffweave3 * element[0] + coeffweave4;
peaksforshow.push([weave, element[1], element[0]]);
this.Peaks.push([weave, element[1], element[0], false, 0])
if (!this.Peaks.some(p => p[2] === element[0])) { this.Peaks.push([weave, element[1], element[0], false, 0]); }
peakformatch.push(element[0]);
});
peakformatch.sort(function (a, b) {
@ -336,8 +351,18 @@ export default {
}
else if (specindex == 1) {
let dataforpeak = this.DataDown.data;
var peaks = await invoke("find_peek", { data: dataforpeak, minheigh: 3000 });
let originalDataDown = this.DataDown.data;
// Step 1: 亚像素插值 (0.05像素间隔20倍数据量)
let resampledDown = await invoke("interpolate_spline_smooth", {
x: Array.from({length: originalDataDown.length}, (_, i) => i),
y: originalDataDown.map(v => Number(v)),
step: 0.05
});
// Step 2: 在插值数据上寻峰
let resampledYDown = resampledDown.map(p => p[1]);
var peaks = await invoke("find_peek", { data: resampledYDown, minheigh: 3000 });
// Step 3: 峰位置映射回原始像素(亚像素精度)
peaks = peaks.map(p => [p[0] / 20, p[1]]);
console.log(peaks);
let peaksforshow = [];
let coeffweave1 = this.Devinfo.bochangxishu2.a0;
@ -355,7 +380,7 @@ export default {
peaks.forEach(element => {
var weave = coeffweave1 * element[0] * element[0] * element[0] + coeffweave2 * element[0] * element[0] + coeffweave3 * element[0] + coeffweave4;
peaksforshow.push([weave, element[1], element[0]]);
this.PeaksDown.push([weave, element[1], element[0], false, 0])
if (!this.PeaksDown.some(p => p[2] === element[0])) { this.PeaksDown.push([weave, element[1], element[0], false, 0]); }
});
@ -394,11 +419,11 @@ export default {
},
async ClearArry(specindex){
if (specindex == 0) {
this.Peaks = [];
this.Peaks = this.Peaks.filter(p => p[3] === true && p[4] !== 0);
this.option.series[1].data = [];
} else if (specindex == 1) {
this.PeaksDown = [];
this.PeaksDown = this.PeaksDown.filter(p => p[3] === true && p[4] !== 0);
this.optiondown.series[1].data = [];
}
@ -584,6 +609,9 @@ export default {
var chart_up = echarts.getInstanceByDom(this.$refs.chart_weavelenth);
chart_up.setOption(this.option);
await this.savePeakResultToCSV(0, orgdata);
this.sensor1Status = "computed";
} else if (spectralnumber == 1) {
this.Devinfo.bochangxishu2.a0 = result[3];
this.Devinfo.bochangxishu2.a1 = result[2];
@ -605,16 +633,105 @@ export default {
var chart_up = echarts.getInstanceByDom(this.$refs.chart_weavelenth_down);
chart_up.setOption(this.optiondown);
await this.savePeakResultToCSV(1, orgdata);
this.sensor2Status = "computed";
}
},
async Set_Weave_Coeff() {
SensorMethod.Set_Weave_Coeff(0, this.Devinfo.bochangxishu.a0, this.Devinfo.bochangxishu.a1, this.Devinfo.bochangxishu.a2, this.Devinfo.bochangxishu.a3);
if (this.Devinfo.fiber_type == "Dual")
SensorMethod.Set_Weave_Coeff(1, this.Devinfo.bochangxishu2.a0, this.Devinfo.bochangxishu2.a1, this.Devinfo.bochangxishu2.a2, this.Devinfo.bochangxishu2.a3);
await SensorMethod.Set_Weave_Coeff(0, this.Devinfo.bochangxishu.a0, this.Devinfo.bochangxishu.a1, this.Devinfo.bochangxishu.a2, this.Devinfo.bochangxishu.a3);
this.sensor1Status = "set";
if (this.Devinfo.fiber_type == "Dual") {
await SensorMethod.Set_Weave_Coeff(1, this.Devinfo.bochangxishu2.a0, this.Devinfo.bochangxishu2.a1, this.Devinfo.bochangxishu2.a2, this.Devinfo.bochangxishu2.a3);
this.sensor2Status = "set";
}
await this.saveCoeffToFile();
},
async saveCoeffToFile() {
const now = new Date();
const pad = n => String(n).padStart(2, '0');
const dateStr = `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
const serial = this.Devinfo?.serialnumber || this.Devinfo?.SerilNumber || "Unknown";
const defaultName = `${serial}_${dateStr}.json`;
const savePath = await dialog.save({
defaultPath: defaultName,
filters: [{ name: "JSON文件", extensions: ["json"] }]
});
if (!savePath) return;
const data = {
serialnumber: serial,
saveTime: now.toISOString(),
sensor_type: this.Devinfo?.sensor_type || "",
bochangxishu: {
a0: this.Devinfo.bochangxishu.a0,
a1: this.Devinfo.bochangxishu.a1,
a2: this.Devinfo.bochangxishu.a2,
a3: this.Devinfo.bochangxishu.a3,
},
bochangxishu2: this.Devinfo.bochangxishu2 ? {
a0: this.Devinfo.bochangxishu2.a0,
a1: this.Devinfo.bochangxishu2.a1,
a2: this.Devinfo.bochangxishu2.a2,
a3: this.Devinfo.bochangxishu2.a3,
} : undefined
};
await fs.writeTextFile(savePath, JSON.stringify(data, null, 2));
},
async savePeakResultToCSV(spectralnumber, orgdata) {
if (!orgdata || orgdata.length === 0) return;
const now = new Date();
const pad = n => String(n).padStart(2, '0');
const dateStr = `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
const serial = this.Devinfo?.serialnumber || this.Devinfo?.SerilNumber || "Unknown";
const isDual = this.Devinfo?.fiber_type === "Dual";
const sensorLabel = spectralnumber === 0 ? "UP" : "DOWN";
let fileSuffix = "";
if (isDual) {
fileSuffix = `_${sensorLabel}`;
}
const defaultName = `${serial}${fileSuffix}_${dateStr}.csv`;
const savePath = await dialog.save({
defaultPath: defaultName,
filters: [{ name: "CSV文件", extensions: ["csv"] }]
});
if (!savePath) return;
const coeff = spectralnumber === 0
? [this.Devinfo.bochangxishu.a0, this.Devinfo.bochangxishu.a1, this.Devinfo.bochangxishu.a2, this.Devinfo.bochangxishu.a3]
: [this.Devinfo.bochangxishu2.a0, this.Devinfo.bochangxishu2.a1, this.Devinfo.bochangxishu2.a2, this.Devinfo.bochangxishu2.a3];
let lines = [];
lines.push(`# 设备序号: ${serial}`);
lines.push(`# 传感器: ${sensorLabel}`);
lines.push(`# 时间: ${now.toISOString()}`);
for (let i = 0; i < 4; i++) {
lines.push(`# a${i}: ${coeff[i]}`);
}
lines.push("像素,参考波长,拟合波长,误差");
orgdata.forEach(el => {
if (el[3] === true) {
let pix = el[2];
let refWl = el[4];
let fitWl = coeff[0]*pix*pix*pix + coeff[1]*pix*pix + coeff[2]*pix + coeff[3];
let err = fitWl - refWl;
let errStr = err >= 0 ? "+" + err.toFixed(3) : err.toFixed(3);
lines.push(`${pix},${refWl},${fitWl.toFixed(3)},${errStr}`);
}
});
await fs.writeTextFile(savePath, lines.join("\n"));
},
/**
* 光谱峰自动匹配函数
* @param {number[]} detectedPeaks - 检测到的峰位置数组(单位:像素或通道号)
@ -707,6 +824,12 @@ export default {
<div class="col-2" style="height: 100%;">
<BCard header="sensor 1" header-text-variant="white" header-tag="header" header-bg-variant="dark"
style="max-width: 20rem">
<template #header>
sensor 1
<a-tag v-if="sensor1Status === 'loaded'" color="blue" size="small" style="float:right">已加载</a-tag>
<a-tag v-if="sensor1Status === 'computed'" color="green" size="small" style="float:right">已计算</a-tag>
<a-tag v-if="sensor1Status === 'set'" color="orange" size="small" style="float:right">已设置</a-tag>
</template>
<BCardText><b-input-group class="my-1">
<BInputGroupPrepend is-text class="myinputprepend">a0</BInputGroupPrepend>
<b-form-input type="number" step="0.00000000000001" v-model="Devinfo.bochangxishu.a0"></b-form-input>
@ -730,6 +853,12 @@ export default {
</BCard>
<BCard header="sensor 2" header-text-variant="white" header-tag="header" header-bg-variant="dark"
style="max-width: 20rem" v-if="Devinfo.fiber_type == 'Dual'">
<template #header>
sensor 2
<a-tag v-if="sensor2Status === 'loaded'" color="blue" size="small" style="float:right">已加载</a-tag>
<a-tag v-if="sensor2Status === 'computed'" color="green" size="small" style="float:right">已计算</a-tag>
<a-tag v-if="sensor2Status === 'set'" color="orange" size="small" style="float:right">已设置</a-tag>
</template>
<BCardText><b-input-group class="my-1">
<BInputGroupPrepend is-text class="myinputprepend">a0</BInputGroupPrepend>
<b-form-input type="number" step="0.00000000000001" v-model="Devinfo.bochangxishu2.a0"></b-form-input>