Compare commits
6 Commits
develop
...
main_devel
| Author | SHA1 | Date | |
|---|---|---|---|
| 35d5f9188a | |||
| 0c01b2127b | |||
| 0cbfb764a7 | |||
| 4ab8863037 | |||
| f62159680e | |||
| 983be8fdef |
Binary file not shown.
@ -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()
|
||||
|
||||
@ -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();
|
||||
@ -182,3 +216,41 @@ fn test_find_peek(){
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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>>();
|
||||
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
"package": {
|
||||
"productName": "SpectralPlot",
|
||||
"version": "0.6.93"
|
||||
"version": "0.6.98"
|
||||
},
|
||||
"tauri": {
|
||||
|
||||
|
||||
@ -5,7 +5,9 @@
|
||||
</a-layout-header>
|
||||
<a-layout>
|
||||
<a-layout-sider :resize-directions="['right']" style=" min-width: 20vw;max-width: 50vw;">
|
||||
<a-dropdown trigger="contextMenu" alignPoint :style="{ display: 'block' }">
|
||||
<a-dropdown ref="contextMenuDropdown" :popup-visible="contextMenuVisible"
|
||||
@popup-visible-change="onContextMenuVisibleChange" trigger="contextMenu" alignPoint
|
||||
:style="{ display: 'block' }">
|
||||
<GuiLeftSider class="lefttree" v-on:NodeClicked="ononeFilechoese"
|
||||
v-on:NodeDblClicked="onFileDblClick" v-on:FilesSelected="onFilesSelected"
|
||||
v-on:FolderClicked="onFolderClicked" v-on:FolderDblClicked="onFolderDblClick"
|
||||
@ -86,9 +88,47 @@ export default {
|
||||
],
|
||||
showSaveDialog: false,
|
||||
filesToSave: [],
|
||||
contextMenuVisible: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('mousedown', this.handleGlobalMouseAction, true)
|
||||
document.addEventListener('wheel', this.handleGlobalMouseAction, true)
|
||||
window.addEventListener('blur', this.closeContextMenu)
|
||||
window.addEventListener('close-app-context-menu', this.closeContextMenu)
|
||||
document.addEventListener('visibilitychange', this.handleVisibilityChange)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('mousedown', this.handleGlobalMouseAction, true)
|
||||
document.removeEventListener('wheel', this.handleGlobalMouseAction, true)
|
||||
window.removeEventListener('blur', this.closeContextMenu)
|
||||
window.removeEventListener('close-app-context-menu', this.closeContextMenu)
|
||||
document.removeEventListener('visibilitychange', this.handleVisibilityChange)
|
||||
},
|
||||
methods: {
|
||||
onContextMenuVisibleChange(visible) {
|
||||
this.contextMenuVisible = visible
|
||||
},
|
||||
closeContextMenu() {
|
||||
this.contextMenuVisible = false
|
||||
},
|
||||
handleVisibilityChange() {
|
||||
if (document.visibilityState !== 'visible') {
|
||||
this.closeContextMenu()
|
||||
}
|
||||
},
|
||||
handleGlobalMouseAction(event) {
|
||||
if (!this.contextMenuVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
const popup = document.querySelector('.arco-trigger-popup')
|
||||
if (popup && popup.contains(event.target)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.closeContextMenu()
|
||||
},
|
||||
// 处理单个文件选择(单击)
|
||||
async ononeFilechoese(filePath) {
|
||||
// console.log('选中文件路径:', filePath);
|
||||
@ -136,6 +176,7 @@ export default {
|
||||
|
||||
// 处理"显示曲线"菜单项点击事件
|
||||
async onShowCurvesClick() {
|
||||
this.closeContextMenu()
|
||||
// console.log('点击显示曲线菜单项');
|
||||
// 如果有多选文件,则加载所有选中的文件
|
||||
if (this.selectedFilePaths && this.selectedFilePaths.length > 0) {
|
||||
@ -185,6 +226,7 @@ export default {
|
||||
this.filesToSave = []
|
||||
},
|
||||
async openCurveSaveDialogForSelectedFiles() {
|
||||
this.closeContextMenu()
|
||||
const paths = (this.selectedFilePaths && this.selectedFilePaths.length > 0)
|
||||
? this.selectedFilePaths
|
||||
: (this.selectedFilePath ? [this.selectedFilePath] : []);
|
||||
|
||||
71
src/DataView/utils/mapLocation.js
Normal file
71
src/DataView/utils/mapLocation.js
Normal file
@ -0,0 +1,71 @@
|
||||
function toFixedNumber(value, digits = 6) {
|
||||
return Number(Number(value).toFixed(digits));
|
||||
}
|
||||
|
||||
function buildSeedFromText(text) {
|
||||
const input = String(text || 'default');
|
||||
let hash = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
hash = ((hash << 5) - hash) + input.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
|
||||
return Math.abs(hash);
|
||||
}
|
||||
|
||||
function buildFallbackCoordinate(seedText) {
|
||||
const seed = buildSeedFromText(seedText);
|
||||
const lonRatio = (seed % 100000) / 100000;
|
||||
const latRatio = (Math.floor(seed / 100000) % 100000) / 100000;
|
||||
|
||||
return {
|
||||
longitude: toFixedNumber(115.7 + (117.4 - 115.7) * lonRatio),
|
||||
latitude: toFixedNumber(40.2 + (40.5 - 40.2) * latRatio)
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeGpsCoordinate(gps) {
|
||||
if (!gps) return null;
|
||||
|
||||
let latitude = Number(gps.latitude);
|
||||
let longitude = Number(gps.longitude);
|
||||
|
||||
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 某些数据源纬经度字段可能写反,优先纠正到合法范围
|
||||
if (Math.abs(latitude) > 90 && Math.abs(longitude) <= 90) {
|
||||
[latitude, longitude] = [longitude, latitude];
|
||||
}
|
||||
|
||||
if (Math.abs(latitude) > 90 || Math.abs(longitude) > 180) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
latitude: toFixedNumber(latitude),
|
||||
longitude: toFixedNumber(longitude),
|
||||
altitude: Number.isFinite(Number(gps.altitude)) ? Number(gps.altitude) : null
|
||||
};
|
||||
}
|
||||
|
||||
function resolveDisplayCoordinate(item) {
|
||||
const environmentData = item?.environmentData || {};
|
||||
const normalizedGps = normalizeGpsCoordinate(environmentData.gps);
|
||||
|
||||
if (normalizedGps) {
|
||||
return normalizedGps;
|
||||
}
|
||||
|
||||
return {
|
||||
...buildFallbackCoordinate(environmentData.fileName || item?.name || ''),
|
||||
altitude: null
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
normalizeGpsCoordinate,
|
||||
resolveDisplayCoordinate
|
||||
};
|
||||
@ -5,7 +5,7 @@
|
||||
justify-content: space-between;">
|
||||
<el-row class="secondhang">
|
||||
<GuiForPlotShow @onComBox1="onComBox1" @onComBox2="onComBox2" @legendselectchanged="legendselectchanged"
|
||||
@useDarkDnChanged="onUseDarkDnChanged" ref="ASDPlotShow" class="plotcontainer">
|
||||
@processOptionsChanged="onProcessOptionsChanged" @useDarkDnChanged="onUseDarkDnChanged" ref="ASDPlotShow" class="plotcontainer">
|
||||
</GuiForPlotShow>
|
||||
</el-row>
|
||||
<el-row class="firsthang">
|
||||
@ -40,9 +40,20 @@ defineOptions({
|
||||
name: "GuiForDataShow"
|
||||
});
|
||||
|
||||
function createDefaultProcessConfig() {
|
||||
return {
|
||||
movingAverage: {
|
||||
window: 5,
|
||||
startMethod: 'shrink',
|
||||
endMethod: 'shrink'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const fromData = ref({
|
||||
comBox1: spectralTypeList[0].value,
|
||||
comBox2: '',
|
||||
processConfig: createDefaultProcessConfig()
|
||||
});
|
||||
const useDarkDn = ref(true);
|
||||
const ASDPlotShow = ref(null);
|
||||
@ -53,6 +64,148 @@ const spectralDataForInfo = ref([]); // 新增:专供 Info/图片使用的数
|
||||
const imgeList = ref([]);
|
||||
const fileData = ref([]);
|
||||
|
||||
function getTypeLabel(type) {
|
||||
return spectralTypeList.find(item => item.value === type)?.label || type || '';
|
||||
}
|
||||
|
||||
function formatMissingItem(type) {
|
||||
if (type === 'flat_ref') {
|
||||
return '白板校准文件原数据(flat_ref)';
|
||||
}
|
||||
const label = getTypeLabel(type);
|
||||
if (!label) return '';
|
||||
if (!type || label === type) return label;
|
||||
return `${label}(${type})`;
|
||||
}
|
||||
|
||||
function getAvailableRawTypes(irisData) {
|
||||
const set = new Set();
|
||||
const sections = irisData?.spectral_data_section || [];
|
||||
for (const element of sections) {
|
||||
const name = (element?.name || '').toString().toLowerCase();
|
||||
if (!name) continue;
|
||||
if (name.includes('ground_dn')) set.add('ground_dn');
|
||||
if (name.includes('flat_dn')) set.add('flat_dn');
|
||||
if (name.includes('dark_dn')) set.add('dark_dn');
|
||||
if (name.includes('flat_ref')) set.add('flat_ref');
|
||||
if (name.includes('gain')) set.add('gain');
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
function getRequiredRawTypesForSpectralType(spectralType, useDarkDn) {
|
||||
const needDark = !!useDarkDn;
|
||||
const addDark = (arr) => (needDark ? [...arr, 'dark_dn'] : arr);
|
||||
|
||||
switch ((spectralType || '').toString()) {
|
||||
case 'ground_dn':
|
||||
return ['ground_dn'];
|
||||
case 'flat_dn':
|
||||
return ['flat_dn'];
|
||||
case 'dark_dn':
|
||||
return ['dark_dn'];
|
||||
case 'gain':
|
||||
return ['gain'];
|
||||
case 'bbjzwj':
|
||||
return ['flat_ref'];
|
||||
case 'flat_ref':
|
||||
return addDark(['ground_dn', 'flat_dn']);
|
||||
case 'radiance_ground':
|
||||
return addDark(['gain', 'ground_dn']);
|
||||
case 'radiance_flat':
|
||||
return addDark(['gain', 'flat_dn']);
|
||||
case 'refrad':
|
||||
return addDark(['gain', 'ground_dn', 'flat_dn']);
|
||||
case 'ref_abs':
|
||||
return addDark(['ground_dn', 'flat_dn', 'flat_ref']);
|
||||
case 'fszd':
|
||||
return addDark(['gain', 'ground_dn']);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function parseFiberValue(value) {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
||||
if (typeof value === 'string') {
|
||||
const match = value.match(/-?\d+(\.\d+)?/);
|
||||
if (match) {
|
||||
const n = Number(match[0]);
|
||||
if (Number.isFinite(n)) return n;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseFiberFromCaliFilePath(value) {
|
||||
if (typeof value !== 'string' || !value) return undefined;
|
||||
const match = value.match(/_(\d+(?:\.\d+)?)d_/i);
|
||||
if (!match) return undefined;
|
||||
const n = Number(match[1]);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
function isIrradianceMode(irisData) {
|
||||
for (const info of irisData?.spectral_info_section || []) {
|
||||
const explicit = parseFiberValue(info?.ForeOptics);
|
||||
const parsed = explicit ?? parseFiberFromCaliFilePath(info?.cailifilePath) ?? parseFiberFromCaliFilePath(info?.califilePath);
|
||||
if (parsed === undefined) continue;
|
||||
const rounded = Math.round(Number(parsed));
|
||||
return rounded === 180 || rounded === 360;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function buildNoDataDetailMessage(files, spectralType, useDarkDn) {
|
||||
const usableFiles = (files || []).filter(file => file?.data);
|
||||
const fileCount = usableFiles.length;
|
||||
const required = getRequiredRawTypesForSpectralType(spectralType, useDarkDn);
|
||||
const missingCounts = new Map();
|
||||
const blockedCounts = new Map();
|
||||
|
||||
for (const file of usableFiles) {
|
||||
const present = getAvailableRawTypes(file.data);
|
||||
for (const dep of required) {
|
||||
if (!present.has(dep)) {
|
||||
missingCounts.set(dep, (missingCounts.get(dep) || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (spectralType === 'radiance_ground' && isIrradianceMode(file.data)) {
|
||||
blockedCounts.set('辐射照度模式不生成地物辐射亮度(radiance_ground)', (blockedCounts.get('辐射照度模式不生成地物辐射亮度(radiance_ground)') || 0) + 1);
|
||||
}
|
||||
if (spectralType === 'fszd' && !isIrradianceMode(file.data)) {
|
||||
blockedCounts.set('非辐射照度模式不生成辐射照度(fszd)', (blockedCounts.get('非辐射照度模式不生成辐射照度(fszd)') || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const missingList = Array.from(missingCounts.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([dep, count]) => {
|
||||
const formatted = formatMissingItem(dep);
|
||||
if (!formatted) return '';
|
||||
return count === fileCount ? formatted : `${formatted}(部分文件)`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const blockedList = Array.from(blockedCounts.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([reason, count]) => (count === fileCount ? reason : `${reason}(部分文件)`));
|
||||
|
||||
const parts = [];
|
||||
if (missingList.length) parts.push(`缺少:${missingList.join('、')}`);
|
||||
if (blockedList.length) parts.push(`原因:${blockedList.join('、')}`);
|
||||
return parts.join(';');
|
||||
}
|
||||
|
||||
function warnNoData() {
|
||||
const spectralType = fromData.value.comBox1;
|
||||
const typeLabel = getTypeLabel(spectralType);
|
||||
const detail = buildNoDataDetailMessage(fileData.value, spectralType, useDarkDn.value);
|
||||
ElMessage.warning(detail ? `当前没有${typeLabel}数据;${detail}` : `当前没有${typeLabel}数据`);
|
||||
}
|
||||
|
||||
async function onloaddata(data) {
|
||||
// 重置数据
|
||||
spectralDataList.value = [];
|
||||
@ -67,13 +220,11 @@ async function onloaddata(data) {
|
||||
try {
|
||||
// 加载文件数据
|
||||
fileData.value = await SpectralDataService.loadFileData(data);
|
||||
|
||||
// 处理数据
|
||||
await processSpectralData();
|
||||
|
||||
if (!spectralDataList.value || spectralDataList.value.length === 0) {
|
||||
const typeLabel = spectralTypeList.find(i => i.value === fromData.value.comBox1)?.label || fromData.value.comBox1;
|
||||
ElMessage.warning(`当前数据没有${typeLabel}数据`);
|
||||
warnNoData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
@ -87,7 +238,8 @@ async function processSpectralData() {
|
||||
fileData.value,
|
||||
fromData.value.comBox1,
|
||||
fromData.value.comBox2,
|
||||
useDarkDn.value
|
||||
useDarkDn.value,
|
||||
fromData.value.processConfig
|
||||
);
|
||||
|
||||
|
||||
@ -143,8 +295,7 @@ const onComBox1 = async (e) => {
|
||||
await processSpectralData();
|
||||
|
||||
if (!spectralDataList.value || spectralDataList.value.length === 0) {
|
||||
const typeLabel = spectralTypeList.find(i => i.value === fromData.value.comBox1)?.label || fromData.value.comBox1;
|
||||
ElMessage.warning(`当前数据没有${typeLabel}数据`);
|
||||
warnNoData();
|
||||
}
|
||||
};
|
||||
|
||||
@ -155,8 +306,26 @@ const onComBox2 = async (val) => {
|
||||
await processSpectralData();
|
||||
|
||||
if (!spectralDataList.value || spectralDataList.value.length === 0) {
|
||||
const typeLabel = spectralTypeList.find(i => i.value === fromData.value.comBox1)?.label || fromData.value.comBox1;
|
||||
ElMessage.warning(`当前数据没有${typeLabel}数据`);
|
||||
warnNoData();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onProcessOptionsChanged = async (config) => {
|
||||
const nextConfig = createDefaultProcessConfig();
|
||||
if (config?.movingAverage) {
|
||||
nextConfig.movingAverage = {
|
||||
...nextConfig.movingAverage,
|
||||
...config.movingAverage
|
||||
};
|
||||
}
|
||||
fromData.value.processConfig = nextConfig;
|
||||
|
||||
if (fromData.value.comBox2 === 'MA' && fromData.value.comBox1) {
|
||||
await processSpectralData();
|
||||
|
||||
if (!spectralDataList.value || spectralDataList.value.length === 0) {
|
||||
warnNoData();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@ import { Icon, Style, Text, Fill, Stroke } from 'ol/style';
|
||||
import { fromLonLat } from 'ol/proj';
|
||||
import pinIcon from '../assets/图钉.png';
|
||||
import { boundingExtent } from 'ol/extent';
|
||||
import { resolveDisplayCoordinate } from '../utils/mapLocation';
|
||||
|
||||
const props = defineProps({
|
||||
dataListMap: {
|
||||
@ -37,10 +38,64 @@ const map = ref(null);
|
||||
const vectorLayer = ref(null);
|
||||
const isMapReady = ref(false);
|
||||
const isFullscreen = ref(false);
|
||||
const DEFAULT_FALLBACK_LATITUDE = 39.0856735;
|
||||
const DEFAULT_FALLBACK_LONGITUDE = 117.1951073;
|
||||
const DEFAULT_FALLBACK_LATITUDE_OFFSET = 0.08;
|
||||
const DEFAULT_FALLBACK_LONGITUDE_OFFSET = 0.12;
|
||||
|
||||
function isValidMapCoordinate(longitude, latitude) {
|
||||
const lng = Number(longitude);
|
||||
const lat = Number(latitude);
|
||||
return Number.isFinite(lng) && Number.isFinite(lat) && lng !== 0 && lat !== 0;
|
||||
}
|
||||
|
||||
function hashStringToSeed(value) {
|
||||
const text = String(value || '');
|
||||
let hash = 0;
|
||||
for (let index = 0; index < text.length; index += 1) {
|
||||
hash = ((hash << 5) - hash) + text.charCodeAt(index);
|
||||
hash |= 0;
|
||||
}
|
||||
return Math.abs(hash);
|
||||
}
|
||||
|
||||
function createStableRandom(seed) {
|
||||
const normalizedSeed = (seed % 2147483647) || 1;
|
||||
let currentSeed = normalizedSeed;
|
||||
return () => {
|
||||
currentSeed = (currentSeed * 16807) % 2147483647;
|
||||
return (currentSeed - 1) / 2147483646;
|
||||
};
|
||||
}
|
||||
|
||||
function getFallbackCoordinateForMap(item, index) {
|
||||
const fileName = item?.environmentData?.fileName || item?.name || `fallback-${index}`;
|
||||
const random = createStableRandom(hashStringToSeed(fileName));
|
||||
const longitudeOffset = (random() - 0.5) * DEFAULT_FALLBACK_LONGITUDE_OFFSET;
|
||||
const latitudeOffset = (random() - 0.5) * DEFAULT_FALLBACK_LATITUDE_OFFSET;
|
||||
|
||||
return {
|
||||
longitude: DEFAULT_FALLBACK_LONGITUDE + longitudeOffset,
|
||||
latitude: DEFAULT_FALLBACK_LATITUDE + latitudeOffset
|
||||
};
|
||||
}
|
||||
|
||||
function getDisplayCoordinateForMap(item, index) {
|
||||
const resolvedCoordinate = resolveDisplayCoordinate(item);
|
||||
if (isValidMapCoordinate(resolvedCoordinate?.longitude, resolvedCoordinate?.latitude)) {
|
||||
return {
|
||||
longitude: Number(resolvedCoordinate.longitude),
|
||||
latitude: Number(resolvedCoordinate.latitude)
|
||||
};
|
||||
}
|
||||
|
||||
// 仅用于地图显示保底,不回写源数据,避免影响导出等真实数据流程。
|
||||
return getFallbackCoordinateForMap(item, index);
|
||||
}
|
||||
|
||||
|
||||
const initializeMap = () => {
|
||||
const center = fromLonLat([116.255535, 40.204654]);
|
||||
const center = fromLonLat([DEFAULT_FALLBACK_LONGITUDE, DEFAULT_FALLBACK_LATITUDE]);
|
||||
|
||||
const view = new View({
|
||||
center,
|
||||
@ -78,33 +133,18 @@ const initializeMap = () => {
|
||||
isMapReady.value = true;
|
||||
};
|
||||
|
||||
const getRandomCoordinateInBeijing = () => {
|
||||
const minLon = 115.7;
|
||||
const maxLon = 117.4;
|
||||
const minLat = 40.2;
|
||||
const maxLat = 40.5;
|
||||
|
||||
const lon = Math.random() * (maxLon - minLon) + minLon;
|
||||
const lat = Math.random() * (maxLat - minLat) + minLat;
|
||||
|
||||
return {
|
||||
longitude: parseFloat(lon.toFixed(6)),
|
||||
latitude: parseFloat(lat.toFixed(6))
|
||||
};
|
||||
};
|
||||
|
||||
const handleTagging = (data) => {
|
||||
if (!isMapReady.value || !Array.isArray(data)) return;
|
||||
|
||||
const features = [];
|
||||
const coords = [];
|
||||
|
||||
data.forEach(item => {
|
||||
const { longitude, latitude } = getRandomCoordinateInBeijing(); // 模拟
|
||||
if (longitude && latitude) {
|
||||
data.forEach((item, index) => {
|
||||
const { longitude, latitude } = getDisplayCoordinateForMap(item, index);
|
||||
if (isValidMapCoordinate(longitude, latitude)) {
|
||||
const feature = new Feature({
|
||||
geometry: new Point(fromLonLat([longitude, latitude])),
|
||||
name: item.environmentData.fileName || ''
|
||||
name: item?.environmentData?.fileName || item?.name || ''
|
||||
});
|
||||
|
||||
feature.setStyle(
|
||||
@ -115,7 +155,7 @@ const handleTagging = (data) => {
|
||||
scale: 1
|
||||
}),
|
||||
text: new Text({
|
||||
text: item.environmentData.fileName || '',
|
||||
text: item?.environmentData?.fileName || item?.name || '',
|
||||
offsetY: 14,
|
||||
font: '300 14px sans-serif',
|
||||
fill: new Fill({
|
||||
@ -144,8 +184,9 @@ const handleTagging = (data) => {
|
||||
}
|
||||
});
|
||||
|
||||
vectorLayer.value?.getSource()?.clear();
|
||||
|
||||
if (features.length > 0) {
|
||||
vectorLayer.value.getSource().clear();
|
||||
vectorLayer.value.getSource().addFeatures(features);
|
||||
|
||||
const extent = boundingExtent(coords);
|
||||
|
||||
693
src/components/menubox/ReflectanceToIris.vue
Normal file
693
src/components/menubox/ReflectanceToIris.vue
Normal 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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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, // 使用二维数组表示数据点的坐标
|
||||
@ -206,6 +208,37 @@ export default {
|
||||
|
||||
// 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>
|
||||
|
||||
@ -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,14 +633,103 @@ 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"));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -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>
|
||||
|
||||
@ -21,31 +21,59 @@ class getIrisDataDispose {
|
||||
this.environmentData = element;
|
||||
}
|
||||
}
|
||||
|
||||
this.trimOffset = this.computeTrimOffset();
|
||||
}
|
||||
|
||||
computeTrimOffset() {
|
||||
const bands = Number(this.devinfoData?.bandnum ?? 0);
|
||||
if (bands < 3) return 0;
|
||||
const firstSensorId = this.irisData?.spectral_info_section?.[0]?.sensor_id;
|
||||
return /is3/i.test(String(firstSensorId || "")) ? 3 : 0;
|
||||
}
|
||||
|
||||
getEffectiveBandnum() {
|
||||
const bands = Number(this.devinfoData?.bandnum ?? 0);
|
||||
return Math.max(0, bands - (this.trimOffset || 0));
|
||||
}
|
||||
|
||||
toNormalArray(element) {
|
||||
const typedArray = manageSpectralData(element, this.devinfoData);
|
||||
const normalArray = typedArray ? Array.from(typedArray) : [];
|
||||
if ((this.trimOffset || 0) <= 0) return normalArray;
|
||||
const end = Math.max(0, normalArray.length - this.trimOffset);
|
||||
return normalArray.slice(0, end);
|
||||
}
|
||||
|
||||
initData() {
|
||||
const basicTypes = ["ground_dn", "flat_dn", "dark_dn", "gain"];
|
||||
const basicTypes = ["ground_dn", "flat_dn", "dark_dn", "gain", "bbjzwj"];
|
||||
const specialTypes = [
|
||||
"flat_ref",
|
||||
"radiance_ground",
|
||||
"radiance_flat",
|
||||
"refrad",
|
||||
"ref_abs",
|
||||
"fszd",
|
||||
];
|
||||
|
||||
let a = null;
|
||||
if (basicTypes.includes(this.spectralName)) {
|
||||
return this.getFileSpectralData();
|
||||
a = this.getFileSpectralData();
|
||||
} else if (specialTypes.includes(this.spectralName)) {
|
||||
return this.getSpecialFileSpectralData();
|
||||
a = this.getSpecialFileSpectralData();
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
//获取一般后端直接返回的类型
|
||||
getFileSpectralData() {
|
||||
// 基础类型适配暗噪开关:ground_dn / flat_dn 在启用暗噪时做 “- dark_dn”
|
||||
const nameLower = (this.spectralName || '').toLowerCase();
|
||||
const bands = this.devinfoData?.bandnum ?? 0;
|
||||
const matchLower = nameLower === 'bbjzwj' ? 'flat_ref' : nameLower;
|
||||
const outputName = nameLower;
|
||||
const bands = this.getEffectiveBandnum();
|
||||
const needDarkCorr =
|
||||
this.useDarkDn && (nameLower === 'ground_dn' || nameLower === 'flat_dn');
|
||||
this.useDarkDn && (matchLower === 'ground_dn' || matchLower === 'flat_dn');
|
||||
|
||||
if (needDarkCorr) {
|
||||
let mainEl = null; // ground_dn 或 flat_dn 的原始 element
|
||||
@ -53,7 +81,7 @@ class getIrisDataDispose {
|
||||
|
||||
for (const element of this.spectral_data) {
|
||||
const lower = (element.name || '').toLowerCase();
|
||||
if (!mainEl && lower.includes(nameLower)) {
|
||||
if (!mainEl && lower.includes(matchLower)) {
|
||||
mainEl = element;
|
||||
} else if (!darkEl && lower.includes('dark_dn')) {
|
||||
darkEl = element;
|
||||
@ -66,10 +94,8 @@ class getIrisDataDispose {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mainTyped = manageSpectralData(mainEl, this.devinfoData);
|
||||
const main = Array.isArray(mainTyped) ? mainTyped : Array.from(mainTyped || []);
|
||||
const darkTyped = darkEl ? manageSpectralData(darkEl, this.devinfoData) : null;
|
||||
const dark = darkTyped ? (Array.isArray(darkTyped) ? darkTyped : Array.from(darkTyped)) : null;
|
||||
const main = this.toNormalArray(mainEl);
|
||||
const dark = darkEl ? this.toNormalArray(darkEl) : null;
|
||||
|
||||
const corrected = [];
|
||||
const invalidCount = { missing: 0, invalidResult: 0 };
|
||||
@ -106,15 +132,21 @@ class getIrisDataDispose {
|
||||
);
|
||||
}
|
||||
|
||||
return { ...mainEl, normalArray: corrected };
|
||||
return { ...mainEl, name: outputName, normalArray: corrected };
|
||||
}
|
||||
|
||||
// 默认路径:保持原逻辑(未启用暗噪或其它基础类型)
|
||||
for (const element of this.spectral_data) {
|
||||
const typedArray = manageSpectralData(element, this.devinfoData);
|
||||
const normalArray = Array.from(typedArray);
|
||||
if ((element.name || '').toLowerCase().includes(nameLower)) {
|
||||
return { ...element, normalArray };
|
||||
const normalArray = this.toNormalArray(element);
|
||||
if ((element.name || '').toLowerCase().includes(matchLower)) {
|
||||
if (nameLower === 'bbjzwj') {
|
||||
const sanitized = normalArray.map((v) => {
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
});
|
||||
return { ...element, name: outputName, normalArray: sanitized };
|
||||
}
|
||||
return { ...element, name: outputName, normalArray };
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -130,16 +162,18 @@ class getIrisDataDispose {
|
||||
["radiance_ground", null],
|
||||
["radiance_flat", null],
|
||||
["refrad", null],
|
||||
["fszd", null],
|
||||
]);
|
||||
for (const element of this.spectral_data) {
|
||||
const typedArray = manageSpectralData(element, this.devinfoData);
|
||||
const normalArray = Array.from(typedArray);
|
||||
const normalArray = this.toNormalArray(element);
|
||||
if (element.name.toLowerCase().includes("ground_dn")) {
|
||||
spectralDataMap.set("ground_dn", { ...element, normalArray });
|
||||
} else if (element.name.toLowerCase().includes("flat_dn")) {
|
||||
spectralDataMap.set("flat_dn", { ...element, normalArray });
|
||||
} else if (element.name.toLowerCase().includes("dark_dn")) {
|
||||
spectralDataMap.set("dark_dn", { ...element, normalArray });
|
||||
} else if (element.name.toLowerCase().includes("flat_ref")) {
|
||||
spectralDataMap.set("flat_ref", { ...element, normalArray });
|
||||
} else if (element.name.toLowerCase().includes("gain")) {
|
||||
spectralDataMap.set("gain", { ...element, normalArray });
|
||||
}
|
||||
@ -153,11 +187,79 @@ class getIrisDataDispose {
|
||||
for (const k of keys) obj[k] = spectralDataMap.get(k).normalArray;
|
||||
return mergeObjectArrays(obj);
|
||||
};
|
||||
const baseName = (...preferredKeys) => {
|
||||
for (const k of preferredKeys) {
|
||||
if (has(k)) return spectralDataMap.get(k).name.split("_")[0];
|
||||
const outputName = (this.spectralName || '').toLowerCase();
|
||||
|
||||
const parseFiberValue = (value) => {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
||||
if (typeof value === "string") {
|
||||
const match = value.match(/-?\d+(\.\d+)?/);
|
||||
if (match) {
|
||||
const n = Number(match[0]);
|
||||
if (Number.isFinite(n)) return n;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const parseFiberFromCaliFilePath = (value) => {
|
||||
if (typeof value !== "string" || !value) return undefined;
|
||||
const match = value.match(/_(\d+(?:\.\d+)?)d_/i);
|
||||
if (!match) return undefined;
|
||||
const n = Number(match[1]);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
};
|
||||
|
||||
const pickFiber = (...preferredKeys) => {
|
||||
for (const info of this.irisData?.spectral_info_section || []) {
|
||||
const explicit = parseFiberValue(info?.ForeOptics);
|
||||
if (explicit !== undefined) return explicit;
|
||||
|
||||
const fromCaliFilePath =
|
||||
parseFiberFromCaliFilePath(info?.cailifilePath) ??
|
||||
parseFiberFromCaliFilePath(info?.califilePath);
|
||||
if (fromCaliFilePath !== undefined) return fromCaliFilePath;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const isIrradianceMode = () => {
|
||||
const fiber = pickFiber("ground_dn", "flat_dn", "gain", "flat_ref", "dark_dn");
|
||||
if (fiber === undefined) return false;
|
||||
const rounded = Math.round(Number(fiber));
|
||||
return rounded === 180 || rounded === 360;
|
||||
};
|
||||
|
||||
const computeRadiance = (targetKey) => {
|
||||
const deps = ["gain", targetKey].concat(this.useDarkDn ? ["dark_dn"] : []);
|
||||
if (!requireDeps(deps)) return undefined;
|
||||
const arrAll = buildArrAll(deps);
|
||||
const out = [];
|
||||
const invalidCount = { zeroDiv: 0, invalidResult: 0 };
|
||||
|
||||
for (const element of arrAll) {
|
||||
const gainExposure = spectralDataMap.get("gain").exposure;
|
||||
const targetExposure = spectralDataMap.get(targetKey).exposure;
|
||||
|
||||
if (Math.abs(targetExposure) < 1e-10) {
|
||||
invalidCount.zeroDiv++;
|
||||
out.push(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
const exposureRatio = gainExposure / targetExposure;
|
||||
const dnDiff = element[targetKey] - (this.useDarkDn ? element.dark_dn : 0);
|
||||
const value = exposureRatio * (element.gain * dnDiff);
|
||||
|
||||
if (isFinite(value) && !isNaN(value)) {
|
||||
out.push(value);
|
||||
} else {
|
||||
invalidCount.invalidResult++;
|
||||
out.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
return { out, invalidCount };
|
||||
};
|
||||
|
||||
// 结果验证(依赖不够时不会走到这里)
|
||||
@ -170,10 +272,10 @@ class getIrisDataDispose {
|
||||
};
|
||||
|
||||
if (this.spectralName == "radiance_ground") {
|
||||
if (isIrradianceMode()) return undefined;
|
||||
const deps = ["gain", "ground_dn"].concat(this.useDarkDn ? ["dark_dn"] : []);
|
||||
if (!requireDeps(deps)) return undefined; // 依赖不够,直接返回空,保持原逻辑
|
||||
const arrAll = buildArrAll(deps);
|
||||
const name = baseName("ground_dn", "gain");
|
||||
|
||||
const radiance_ground = [];
|
||||
const invalidCount = { zeroDiv: 0, invalidResult: 0 };
|
||||
@ -202,13 +304,12 @@ class getIrisDataDispose {
|
||||
if (invalidCount.zeroDiv > 0 || invalidCount.invalidResult > 0) {
|
||||
console.warn(`radiance_ground计算统计: 除零错误 ${invalidCount.zeroDiv} 个,无效结果 ${invalidCount.invalidResult} 个,有效数据 ${radiance_ground.length} 个`);
|
||||
}
|
||||
return validateResult(radiance_ground, name + "_radiance_ground", "radiance_ground");
|
||||
return validateResult(radiance_ground, outputName, "radiance_ground");
|
||||
|
||||
} else if (this.spectralName == "radiance_flat") {
|
||||
const deps = ["gain", "flat_dn"].concat(this.useDarkDn ? ["dark_dn"] : []);
|
||||
if (!requireDeps(deps)) return undefined; // 依赖不够,直接返回空
|
||||
const arrAll = buildArrAll(deps);
|
||||
const name = baseName("flat_dn", "gain");
|
||||
|
||||
const radiance_flat = [];
|
||||
const invalidCount = { zeroDiv: 0, invalidResult: 0 };
|
||||
@ -237,13 +338,12 @@ class getIrisDataDispose {
|
||||
if (invalidCount.zeroDiv > 0 || invalidCount.invalidResult > 0) {
|
||||
console.warn(`radiance_flat计算统计: 除零错误 ${invalidCount.zeroDiv} 个,无效结果 ${invalidCount.invalidResult} 个,有效数据 ${radiance_flat.length} 个`);
|
||||
}
|
||||
return validateResult(radiance_flat, name + "_radiance_flat", "radiance_flat");
|
||||
return validateResult(radiance_flat, outputName, "radiance_flat");
|
||||
|
||||
} else if (this.spectralName == "refrad") {
|
||||
const deps = ["gain", "ground_dn", "flat_dn"].concat(this.useDarkDn ? ["dark_dn"] : []);
|
||||
if (!requireDeps(deps)) return undefined; // 依赖不够,直接返回空
|
||||
const arrAll = buildArrAll(deps);
|
||||
const name = baseName("ground_dn", "flat_dn", "gain");
|
||||
|
||||
const refrad = [];
|
||||
const invalidCount = { zeroDiv: 0, invalidResult: 0 };
|
||||
@ -284,13 +384,12 @@ class getIrisDataDispose {
|
||||
if (invalidCount.zeroDiv > 0 || invalidCount.invalidResult > 0) {
|
||||
console.warn(`refrad计算统计: 除零错误 ${invalidCount.zeroDiv} 个,无效结果 ${invalidCount.invalidResult} 个,有效数据 ${refrad.length} 个`);
|
||||
}
|
||||
return validateResult(refrad, name + "_refrad", "refrad");
|
||||
return validateResult(refrad, outputName, "refrad");
|
||||
|
||||
} else if (this.spectralName == "flat_ref") {
|
||||
const deps = ["ground_dn", "flat_dn"].concat(this.useDarkDn ? ["dark_dn"] : []);
|
||||
if (!requireDeps(deps)) return undefined; // 依赖不够,直接返回空
|
||||
const arrAll = buildArrAll(deps);
|
||||
const name = baseName("ground_dn", "flat_dn");
|
||||
|
||||
const validData = [];
|
||||
const invalidCount = { zeroDiv: 0, invalidResult: 0 };
|
||||
@ -315,13 +414,61 @@ class getIrisDataDispose {
|
||||
if (invalidCount.zeroDiv > 0 || invalidCount.invalidResult > 0) {
|
||||
console.warn(`flat_ref计算统计: 除零错误 ${invalidCount.zeroDiv} 个,无效结果 ${invalidCount.invalidResult} 个,有效数据 ${validData.length} 个`);
|
||||
}
|
||||
return validateResult(validData, name + "_flat_ref", "flat_ref");
|
||||
return validateResult(validData, outputName, "flat_ref");
|
||||
} else if (this.spectralName == "ref_abs") {
|
||||
const deps = ["ground_dn", "flat_dn", "flat_ref"].concat(this.useDarkDn ? ["dark_dn"] : []);
|
||||
if (!requireDeps(deps)) return undefined;
|
||||
const arrAll = buildArrAll(deps);
|
||||
|
||||
const ref_abs = [];
|
||||
const invalidCount = { zeroDiv: 0, invalidResult: 0 };
|
||||
|
||||
for (const element of arrAll) {
|
||||
const denominator = element.flat_dn - (this.useDarkDn ? element.dark_dn : 0);
|
||||
const numerator = element.ground_dn - (this.useDarkDn ? element.dark_dn : 0);
|
||||
|
||||
if (Math.abs(denominator) < 1e-10) {
|
||||
invalidCount.zeroDiv++;
|
||||
ref_abs.push(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ratio = numerator / denominator;
|
||||
const calib = Number(element.flat_ref);
|
||||
const value = ratio * (Number.isFinite(calib) ? calib : 0);
|
||||
|
||||
if (isFinite(value) && !isNaN(value)) {
|
||||
ref_abs.push(value);
|
||||
} else {
|
||||
invalidCount.invalidResult++;
|
||||
ref_abs.push(0);
|
||||
}
|
||||
}
|
||||
if (invalidCount.zeroDiv > 0 || invalidCount.invalidResult > 0) {
|
||||
console.warn(`ref_abs计算统计: 除零错误 ${invalidCount.zeroDiv} 个,无效结果 ${invalidCount.invalidResult} 个,有效数据 ${ref_abs.length} 个`);
|
||||
}
|
||||
return validateResult(ref_abs, outputName, "ref_abs");
|
||||
} else if (this.spectralName == "fszd") {
|
||||
if (!isIrradianceMode()) return undefined;
|
||||
|
||||
if (!has("ground_dn")) return undefined;
|
||||
|
||||
const computed = computeRadiance("ground_dn");
|
||||
if (!computed) return undefined;
|
||||
|
||||
const typeLabel = "fszd";
|
||||
|
||||
if (computed.invalidCount.zeroDiv > 0 || computed.invalidCount.invalidResult > 0) {
|
||||
console.warn(`fszd计算统计: 除零错误 ${computed.invalidCount.zeroDiv} 个,无效结果 ${computed.invalidCount.invalidResult} 个,有效数据 ${computed.out.length} 个`);
|
||||
}
|
||||
return validateResult(computed.out, outputName, typeLabel);
|
||||
}
|
||||
}
|
||||
|
||||
getSpectralInfoData() {
|
||||
let spectralInfoData = [];
|
||||
for (let i = 0; i < this.devinfoData.bandnum; i++) {
|
||||
const bands = this.getEffectiveBandnum();
|
||||
for (let i = 0; i < bands; i++) {
|
||||
const a =
|
||||
this.devinfoData.wave_coeff.a1 * i ** 3 +
|
||||
this.devinfoData.wave_coeff.a2 * i * i +
|
||||
@ -414,10 +561,9 @@ const manageSpectralData = (rawData, devinfoData) => {
|
||||
case 0x14: // uint32
|
||||
return new Uint32Array(uint8Array.buffer, 0, bands);
|
||||
default:
|
||||
throw new Error(`Unsupported data type: 0x${dataType.toString(16)}`);
|
||||
throw new Error(`Unsupported data type: 0x${rawData.data_type.toString(16)}`);
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = `Failed to parse spectral data: ${e.message}`;
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@ -455,18 +601,18 @@ const spectralTypeList = [
|
||||
// value: "refrad",
|
||||
// label: "反射率能量",
|
||||
// },
|
||||
// {
|
||||
// value: "jdfs",
|
||||
// label: "绝对反射率",
|
||||
// },
|
||||
// {
|
||||
// value: "ckbzwj",
|
||||
// label: "参考校准文件",
|
||||
// },
|
||||
// {
|
||||
// value: "fszd",
|
||||
// label: "辐射照度",
|
||||
// },
|
||||
{
|
||||
value: "ref_abs",
|
||||
label: "绝对反射率",
|
||||
},
|
||||
{
|
||||
value: "bbjzwj",
|
||||
label: "白板校准文件",
|
||||
},
|
||||
{
|
||||
value: "fszd",
|
||||
label: "辐射照度",
|
||||
},
|
||||
];
|
||||
|
||||
const spectralProcessTypeList = [
|
||||
@ -485,6 +631,9 @@ const spectralProcessTypeList = [
|
||||
];
|
||||
|
||||
function D1(arr) {
|
||||
if (!Array.isArray(arr) || arr.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const result = [];
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
result.push(arr[i] - arr[i - 1]);
|
||||
@ -494,6 +643,9 @@ function D1(arr) {
|
||||
}
|
||||
|
||||
function D2(arr) {
|
||||
if (!Array.isArray(arr) || arr.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const arr2 = D1(arr);
|
||||
return D1(arr2);
|
||||
}
|
||||
|
||||
@ -1,6 +1,92 @@
|
||||
import { getIrisDataDispose, D1, D2 } from '../utils/irisDataDispose';
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
|
||||
const DEFAULT_MOVING_AVERAGE_OPTIONS = Object.freeze({
|
||||
window: 5,
|
||||
startMethod: 'shrink',
|
||||
endMethod: 'shrink'
|
||||
});
|
||||
|
||||
function normalizeMovingAverageOptions(options = {}) {
|
||||
let window = Number(options.window);
|
||||
if (!Number.isFinite(window) || window < 1) {
|
||||
window = DEFAULT_MOVING_AVERAGE_OPTIONS.window;
|
||||
}
|
||||
window = Math.max(1, Math.round(window));
|
||||
if (window % 2 === 0) {
|
||||
window += 1;
|
||||
}
|
||||
|
||||
const startMethod = ['shrink', 'pad', 'keep'].includes(options.startMethod)
|
||||
? options.startMethod
|
||||
: DEFAULT_MOVING_AVERAGE_OPTIONS.startMethod;
|
||||
const endMethod = ['shrink', 'pad', 'keep'].includes(options.endMethod)
|
||||
? options.endMethod
|
||||
: DEFAULT_MOVING_AVERAGE_OPTIONS.endMethod;
|
||||
|
||||
return {
|
||||
window,
|
||||
startMethod,
|
||||
endMethod
|
||||
};
|
||||
}
|
||||
|
||||
function average(values) {
|
||||
const validValues = values
|
||||
.map(value => Number(value))
|
||||
.filter(value => Number.isFinite(value));
|
||||
|
||||
if (!validValues.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return validValues.reduce((sum, value) => sum + value, 0) / validValues.length;
|
||||
}
|
||||
|
||||
function buildBoundaryValues(value, count, method) {
|
||||
if (count <= 0 || method !== 'pad') {
|
||||
return [];
|
||||
}
|
||||
return Array.from({ length: count }, () => value);
|
||||
}
|
||||
|
||||
function movingAverage(data, options = {}) {
|
||||
if (!Array.isArray(data) || !data.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const normalizedOptions = normalizeMovingAverageOptions(options);
|
||||
const radius = Math.floor(normalizedOptions.window / 2);
|
||||
|
||||
return data.map((currentValue, index) => {
|
||||
const startIndex = index - radius;
|
||||
const endIndex = index + radius;
|
||||
const missingLeft = Math.max(0, 0 - startIndex);
|
||||
const missingRight = Math.max(0, endIndex - (data.length - 1));
|
||||
|
||||
if (
|
||||
(missingLeft > 0 && normalizedOptions.startMethod === 'keep') ||
|
||||
(missingRight > 0 && normalizedOptions.endMethod === 'keep')
|
||||
) {
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
const values = [];
|
||||
|
||||
if (missingLeft > 0) {
|
||||
values.push(...buildBoundaryValues(data[0], missingLeft, normalizedOptions.startMethod));
|
||||
}
|
||||
|
||||
values.push(...data.slice(Math.max(0, startIndex), Math.min(data.length, endIndex + 1)));
|
||||
|
||||
if (missingRight > 0) {
|
||||
values.push(...buildBoundaryValues(data[data.length - 1], missingRight, normalizedOptions.endMethod));
|
||||
}
|
||||
|
||||
return average(values);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 光谱数据处理服务
|
||||
*/
|
||||
@ -17,10 +103,13 @@ export class SpectralDataService {
|
||||
* 加载文件数据
|
||||
*/
|
||||
static async loadFileData(filePaths) {
|
||||
|
||||
const fileData = [];
|
||||
for (const path of filePaths) {
|
||||
try {
|
||||
const src = await invoke("getoneirisfile", { path });
|
||||
console.log('原始文件:', src);
|
||||
|
||||
const fileName = this.extractFileName(path);
|
||||
fileData.push({ data: src, name: fileName });
|
||||
} catch (error) {
|
||||
@ -33,7 +122,7 @@ export class SpectralDataService {
|
||||
/**
|
||||
* 处理光谱数据
|
||||
*/
|
||||
static processSpectralData(fileData, spectralType, processType = '', useDarkDn = true) {
|
||||
static processSpectralData(fileData, spectralType, processType = '', useDarkDn = true, processOptions = {}) {
|
||||
const spectralDataList = [];
|
||||
const imageList = [];
|
||||
|
||||
@ -55,11 +144,18 @@ export class SpectralDataService {
|
||||
url: 'jzsb'
|
||||
}]);
|
||||
}
|
||||
if (datay?.normalArray && datax) {
|
||||
const y = datay?.normalArray;
|
||||
if (Array.isArray(y) && y.length > 0 && Array.isArray(datax) && datax.length > 0) {
|
||||
if (datax.length !== y.length) {
|
||||
console.warn(`波长/数据长度不一致: ${element.name} datax=${datax.length} datay=${y.length}`);
|
||||
}
|
||||
const fileBaseName = (element.name || '').toString().replace(/\.[^.]+$/, '');
|
||||
const typeSuffix = (datay?.name || spectralType || '').toString();
|
||||
const seriesName = [fileBaseName, typeSuffix].filter(Boolean).join('_');
|
||||
spectralDataList.push({
|
||||
name: datay?.name || '',
|
||||
name: seriesName,
|
||||
datax: datax,
|
||||
datay: datay.normalArray,
|
||||
datay: y,
|
||||
environmentData: { ...environmentData, fileName: element.name }
|
||||
});
|
||||
}
|
||||
@ -72,7 +168,7 @@ export class SpectralDataService {
|
||||
return {
|
||||
spectralDataList,
|
||||
imageList,
|
||||
processedData: this.applyProcessing(spectralDataList, processType)
|
||||
processedData: this.applyProcessing(spectralDataList, processType, processOptions)
|
||||
};
|
||||
}
|
||||
|
||||
@ -80,7 +176,7 @@ export class SpectralDataService {
|
||||
* 应用数据处理(D1/D2等)
|
||||
* 注意:该方法需要在类内部,避免顶层出现 static 关键字导致语法错误
|
||||
*/
|
||||
static applyProcessing(spectralDataList, processType) {
|
||||
static applyProcessing(spectralDataList, processType, processOptions = {}) {
|
||||
if (!processType) return spectralDataList;
|
||||
|
||||
switch (processType) {
|
||||
@ -98,6 +194,15 @@ export class SpectralDataService {
|
||||
datay: D2(item.datay),
|
||||
environmentData: item.environmentData
|
||||
}));
|
||||
case 'MA': {
|
||||
const movingAverageOptions = normalizeMovingAverageOptions(processOptions.movingAverage);
|
||||
return spectralDataList.map(item => ({
|
||||
name: item.name + '_MA',
|
||||
datax: item.datax,
|
||||
datay: movingAverage(item.datay, movingAverageOptions),
|
||||
environmentData: item.environmentData
|
||||
}));
|
||||
}
|
||||
default:
|
||||
return spectralDataList;
|
||||
}
|
||||
@ -106,11 +211,11 @@ export class SpectralDataService {
|
||||
/**
|
||||
* 批量处理选定文件的光谱数据
|
||||
*/
|
||||
static processSelectedFiles(fileData, selectedFileNames, spectralType, processType = '', useDarkDn = true) {
|
||||
static processSelectedFiles(fileData, selectedFileNames, spectralType, processType = '', useDarkDn = true, processOptions = {}) {
|
||||
const filteredData = fileData.filter(file =>
|
||||
selectedFileNames.includes(file.name)
|
||||
);
|
||||
// 透传暗噪开关
|
||||
return this.processSpectralData(filteredData, spectralType, processType, useDarkDn);
|
||||
return this.processSpectralData(filteredData, spectralType, processType, useDarkDn, processOptions);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user