desktop: add pH calibration UI in Calibrate tab
This commit is contained in:
parent
bdb72a9917
commit
d5e1a7dd0f
|
|
@ -998,6 +998,45 @@ impl App {
|
|||
self.status = "No valid EIS data for Rs extraction".into();
|
||||
}
|
||||
}
|
||||
Message::PhCalKnownChanged(s) => { self.ph_cal_known = s; }
|
||||
Message::PhAddCalPoint => {
|
||||
if let Some(peak_mv) = crate::lsv_analysis::detect_qhq_peak(&self.lsv_points) {
|
||||
if let Ok(ph) = self.ph_cal_known.parse::<f32>() {
|
||||
self.ph_cal_points.push((ph, peak_mv));
|
||||
self.status = format!("pH cal point: pH={:.2} peak={:.1} mV ({} pts)",
|
||||
ph, peak_mv, self.ph_cal_points.len());
|
||||
}
|
||||
} else {
|
||||
self.status = "No Q/HQ peak found in LSV data".into();
|
||||
}
|
||||
}
|
||||
Message::PhClearCalPoints => {
|
||||
self.ph_cal_points.clear();
|
||||
self.status = "pH cal points cleared".into();
|
||||
}
|
||||
Message::PhComputeAndSetCal => {
|
||||
if self.ph_cal_points.len() < 2 {
|
||||
self.status = "Need at least 2 calibration points".into();
|
||||
} else {
|
||||
let n = self.ph_cal_points.len() as f32;
|
||||
let mean_ph: f32 = self.ph_cal_points.iter().map(|p| p.0).sum::<f32>() / n;
|
||||
let mean_v: f32 = self.ph_cal_points.iter().map(|p| p.1).sum::<f32>() / n;
|
||||
let num: f32 = self.ph_cal_points.iter()
|
||||
.map(|p| (p.0 - mean_ph) * (p.1 - mean_v)).sum();
|
||||
let den: f32 = self.ph_cal_points.iter()
|
||||
.map(|p| (p.0 - mean_ph).powi(2)).sum();
|
||||
if den.abs() < 1e-12 {
|
||||
self.status = "Degenerate calibration data".into();
|
||||
} else {
|
||||
let slope = num / den;
|
||||
let offset = mean_v - slope * mean_ph;
|
||||
self.ph_slope = Some(slope);
|
||||
self.ph_offset = Some(offset);
|
||||
self.send_cmd(&protocol::build_sysex_set_ph_cal(slope, offset));
|
||||
self.status = format!("pH cal set: slope={:.4} offset={:.4}", slope, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ClCalKnownPpmChanged(s) => { self.cl_cal_known_ppm = s; }
|
||||
Message::ClSetFactor => {
|
||||
let known_ppm = self.cl_cal_known_ppm.parse::<f32>().unwrap_or(0.0);
|
||||
|
|
@ -1750,6 +1789,47 @@ impl App {
|
|||
].spacing(10).align_y(iced::Alignment::End)
|
||||
);
|
||||
|
||||
/* pH calibration */
|
||||
results = results.push(iced::widget::horizontal_rule(1));
|
||||
results = results.push(text("pH Calibration (Q/HQ peak-shift)").size(16));
|
||||
if let (Some(s), Some(o)) = (self.ph_slope, self.ph_offset) {
|
||||
results = results.push(text(format!("slope: {:.4} mV/pH offset: {:.4} mV", s, o)).size(14));
|
||||
if let Some(peak) = crate::lsv_analysis::detect_qhq_peak(&self.lsv_points) {
|
||||
if s.abs() > 1e-6 {
|
||||
let ph = (peak - o) / s;
|
||||
results = results.push(text(format!("Computed pH: {:.2} (peak at {:.1} mV)", ph, peak)).size(14));
|
||||
}
|
||||
}
|
||||
}
|
||||
results = results.push(
|
||||
row![
|
||||
column![
|
||||
text("Known pH").size(12),
|
||||
text_input("7.00", &self.ph_cal_known)
|
||||
.on_input(Message::PhCalKnownChanged).width(80),
|
||||
].spacing(2),
|
||||
button(text("Add Point").size(13))
|
||||
.style(style_action())
|
||||
.padding([6, 16])
|
||||
.on_press(Message::PhAddCalPoint),
|
||||
].spacing(10).align_y(iced::Alignment::End)
|
||||
);
|
||||
for (i, (ph, mv)) in self.ph_cal_points.iter().enumerate() {
|
||||
results = results.push(text(format!(" {}. pH={:.2} peak={:.1} mV", i + 1, ph, mv)).size(13));
|
||||
}
|
||||
results = results.push(
|
||||
row![
|
||||
button(text("Clear Points").size(13))
|
||||
.style(style_action())
|
||||
.padding([6, 16])
|
||||
.on_press(Message::PhClearCalPoints),
|
||||
button(text("Compute & Set pH Cal").size(13))
|
||||
.style(style_action())
|
||||
.padding([6, 16])
|
||||
.on_press(Message::PhComputeAndSetCal),
|
||||
].spacing(10)
|
||||
);
|
||||
|
||||
row![
|
||||
container(inputs).width(Length::FillPortion(2)),
|
||||
iced::widget::vertical_rule(1),
|
||||
|
|
|
|||
Loading…
Reference in New Issue