This commit is contained in:
core 2023-11-06 16:51:04 -05:00
parent a729956898
commit 4b7ec8c058
Signed by: core
GPG Key ID: FDBF740DADDCEECF
8 changed files with 267 additions and 19 deletions

View File

@ -21,6 +21,7 @@ log = "0.4"
wasm-logger = "0.2" wasm-logger = "0.2"
serde-wasm-bindgen = "0.6" serde-wasm-bindgen = "0.6"
chrono = "0.4" chrono = "0.4"
itertools = "0.11"
[dependencies.js-sys] [dependencies.js-sys]
version = "0.3" version = "0.3"

View File

@ -1,13 +1,16 @@
use web_sys::FileReader; use web_sys::FileReader;
use crate::loadar2; use crate::loadar2;
use crate::mode::Mode;
use crate::rendering::render; use crate::rendering::render;
use crate::scope::ScopeState; use crate::scope::ScopeState;
pub fn should_newline(state: &mut ScopeState) -> bool { pub fn should_newline(state: &mut ScopeState) -> bool {
state.command_buf.starts_with("SET MODE") state.command_buf.starts_with("MODE SET")
|| state.command_buf.starts_with("ELEVATION SET")
} }
pub fn exec(state: &mut ScopeState, command: String) { pub fn exec(state: &mut ScopeState, command: String) {
let tokens = command.split(' ').collect::<Vec<_>>();
if command == "CLF OV" { if command == "CLF OV" {
state.file_input.click(); state.file_input.click();
} else if command == "CLF RELOAD" { } else if command == "CLF RELOAD" {
@ -15,6 +18,67 @@ pub fn exec(state: &mut ScopeState, command: String) {
state.command_buf_response_mode = true; state.command_buf_response_mode = true;
render(state).expect("rerender failed"); render(state).expect("rerender failed");
loadar2("file"); loadar2("file");
} else if command.starts_with("MODE SET") {
if tokens.len() < 3 {
state.command_buf = "ARGUMENT INVALID".to_string();
state.command_buf_response_mode = true;
return
} else {
let mode = tokens[2];
if let Some(ar2) = &state.ar2 {
let types = ar2.elevations.get(&state.selected_elevation).unwrap();
let valid_modes: Vec<_> = types[0].available_data.keys().collect();
if !valid_modes.contains(&&mode.to_string()) {
state.command_buf = "ARGUMENT INVALID".to_string();
state.command_buf_response_mode = true;
return
}
state.scope_mode = match mode {
"REF" => Mode::Reflectivity,
"VEL" => Mode::Velocity,
"SW" => Mode::SpectrumWidth,
"ZDR" => Mode::DifferentialReflectivity,
"PHI" => Mode::DifferentialPhase,
"RHO" => Mode::CorrelationCoefficient,
"CFP" => Mode::ClutterFilterPowerRemoved,
_ => unreachable!()
};
} else {
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
state.command_buf_response_mode = true;
return
}
}
} else if command.starts_with("ELEVATION SET") {
if tokens.len() < 3 {
state.command_buf = "ARGUMENT INVALID".to_string();
state.command_buf_response_mode = true;
return
} else {
let elev = tokens[2].to_string();
if let Some(ar2) = &state.ar2 {
let mut valid_elevs = vec![];
for elev in ar2.elevations.keys() {
valid_elevs.push(format!("{:0>3}", elev));
}
if !valid_elevs.contains(&elev) {
state.command_buf = "ARGUMENT INVALID".to_string();
state.command_buf_response_mode = true;
return
}
let number: usize = elev.parse().unwrap();
// reset scope back to reflectivity which is included in every elevation
state.scope_mode = Mode::Reflectivity;
state.selected_elevation = number;
} else {
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
state.command_buf_response_mode = true;
return
}
}
} else { } else {
state.command_buf = "UNRECOGNIZED COMMAND".to_string(); state.command_buf = "UNRECOGNIZED COMMAND".to_string();
state.command_buf_response_mode = true; state.command_buf_response_mode = true;

View File

@ -15,6 +15,7 @@ use wasm_bindgen::prelude::*;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
use crate::command::{exec, should_newline}; use crate::command::{exec, should_newline};
use crate::mode::Mode; use crate::mode::Mode;
use crate::mode::Mode::Reflectivity;
use crate::scope::{Preferences, RenderState, ScopeState}; use crate::scope::{Preferences, RenderState, ScopeState};
#[wasm_bindgen] #[wasm_bindgen]
@ -61,7 +62,8 @@ pub fn __nxrd_browser_init() -> AbiScopeState {
}, },
command_buf: String::new(), command_buf: String::new(),
command_buf_response_mode: false, command_buf_response_mode: false,
new_data_available: false new_data_available: false,
selected_elevation: 1
}; };
info!("nexrad-browser initialized successfully"); info!("nexrad-browser initialized successfully");
@ -83,6 +85,8 @@ pub fn load_ar2(buf: &JsValue, scope: &mut AbiScopeState) {
info!("new data chunk loaded"); info!("new data chunk loaded");
scope.0.ar2 = Some(loaded); scope.0.ar2 = Some(loaded);
scope.0.new_data_available = false; scope.0.new_data_available = false;
scope.0.scope_mode = Reflectivity;
scope.0.command_buf = "NEW DATA LOADED SUCCESSFULLY".to_string();
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -92,7 +96,8 @@ pub fn new_file_available(scope: &mut AbiScopeState) {
#[wasm_bindgen] #[wasm_bindgen]
pub fn keydown(state: &mut AbiScopeState, key: String) { pub fn keydown(state: &mut AbiScopeState, key: String) {
if state.0.command_buf_response_mode { if state.0.command_buf_response_mode && (key == "Backspace" || key.len() == 1) {
info!("resetting, key {} buf {}", key, state.0.command_buf);
state.0.command_buf = String::new(); state.0.command_buf = String::new();
state.0.command_buf_response_mode = false; state.0.command_buf_response_mode = false;
} }
@ -101,8 +106,8 @@ pub fn keydown(state: &mut AbiScopeState, key: String) {
state.0.command_buf = String::new(); state.0.command_buf = String::new();
} else if key == "Enter" { } else if key == "Enter" {
let cmd = state.0.command_buf.clone(); let cmd = state.0.command_buf.clone();
exec(&mut state.0, cmd.replace('\n', " "));
state.0.command_buf = String::new(); state.0.command_buf = String::new();
exec(&mut state.0, cmd.replace('\n', " "));
} else if key == " " && should_newline(&mut state.0) { } else if key == " " && should_newline(&mut state.0) {
state.0.command_buf += "\n"; state.0.command_buf += "\n";
} else if key == "Backspace" { } else if key == "Backspace" {

View File

@ -1,4 +1,4 @@
#[derive(Debug)] #[derive(Debug, PartialEq, Eq)]
pub enum Mode { pub enum Mode {
/// REF /// REF
Reflectivity, Reflectivity,
@ -17,3 +17,42 @@ pub enum Mode {
/// RADR INOP /// RADR INOP
RadarInoperative RadarInoperative
} }
impl Mode {
pub fn unselected(&self) -> &str {
match self {
Self::Reflectivity => " REF ",
Self::Velocity => " VEL ",
Self::SpectrumWidth => " SW ",
Self::DifferentialReflectivity => " ZDR ",
Self::DifferentialPhase => " PHI ",
Self::CorrelationCoefficient => " RHO ",
Self::ClutterFilterPowerRemoved => " CFP ",
Self::RadarInoperative => " RADR INOP"
}
}
pub fn selected(&self) -> &str {
match self {
Self::Reflectivity => ">REF<",
Self::Velocity => ">VEL<",
Self::SpectrumWidth => ">SW <",
Self::DifferentialReflectivity => ">ZDR<",
Self::DifferentialPhase => ">PHI<",
Self::CorrelationCoefficient => ">RHO<",
Self::ClutterFilterPowerRemoved => ">CFP<",
Self::RadarInoperative => ">RADR INOP<"
}
}
pub fn rname(&self) -> &str {
match self {
Self::Reflectivity => "REF",
Self::Velocity => "VEL",
Self::SpectrumWidth => "SW ",
Self::DifferentialReflectivity => "ZDR",
Self::DifferentialPhase => "PHI",
Self::CorrelationCoefficient => "RHO",
Self::ClutterFilterPowerRemoved => "CFP",
Self::RadarInoperative => "NOP"
}
}
}

View File

@ -1,5 +1,9 @@
use chrono::{DateTime, Timelike, Utc}; use chrono::{DateTime, Timelike, Utc};
use itertools::Itertools;
use log::info;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
use crate::mode::Mode;
use crate::mode::Mode::{ClutterFilterPowerRemoved, CorrelationCoefficient, DifferentialPhase, DifferentialReflectivity, RadarInoperative, Reflectivity, SpectrumWidth, Velocity};
use crate::rescaleCanvas; use crate::rescaleCanvas;
use crate::scope::ScopeState; use crate::scope::ScopeState;
use crate::utils::parse_date; use crate::utils::parse_date;
@ -9,6 +13,8 @@ pub const TEXT_COLOR_RED: &str = "#ef0000";
pub const TEXT_COLOR_GREEN: &str = "#4af626"; pub const TEXT_COLOR_GREEN: &str = "#4af626";
pub const TEXT_COLOR_WHITE: &str = "#dedede"; pub const TEXT_COLOR_WHITE: &str = "#dedede";
pub const RANGE: usize = 50;
pub fn time(h: usize, m: usize, s: usize, tag_end: &str) -> String { pub fn time(h: usize, m: usize, s: usize, tag_end: &str) -> String {
format!("{:0>2}:{:0>2}:{:0>2}{}", h, m, s, tag_end) format!("{:0>2}:{:0>2}:{:0>2}{}", h, m, s, tag_end)
} }
@ -69,12 +75,92 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
ctx.fill_text(&format!("{} VCP {} {}", ar2.volume_header_record.icao, ar2.meta_rda_status_data.vcp, vcp_string(ar2.meta_rda_status_data.vcp)), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64))?; ctx.fill_text(&format!("{} VCP {} {}", ar2.volume_header_record.icao, ar2.meta_rda_status_data.vcp, vcp_string(ar2.meta_rda_status_data.vcp)), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64))?;
let recorded = parse_date(ar2.volume_header_record.date, ar2.volume_header_record.time); let recorded = parse_date(ar2.volume_header_record.date, ar2.volume_header_record.time);
let delay = time - recorded; let delay = time - recorded;
ctx.fill_text(&format!("SITE DELAY {}:{}:{}", delay.num_hours(), delay.num_minutes() - (delay.num_hours() * 60), delay.num_seconds() - (delay.num_minutes() - (delay.num_hours() * 60) * 60)), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64 * 2.0))?;
let seconds = delay.num_seconds() % 60;
let minutes = (delay.num_seconds() / 60) % 60;
let hours = (delay.num_seconds() / 60) / 60;
ctx.fill_text(&format!("SITE DELAY {:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64 * 2.0))?;
} else { } else {
ctx.fill_text("SITE INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + state.prefs.fcs as f64)?; ctx.fill_text("SITE INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + state.prefs.fcs as f64)?;
ctx.fill_text("DELAY INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs * 2) as f64)?; ctx.fill_text("DELAY INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs * 2) as f64)?;
} }
// mode info
ctx.fill_text("MODE", (canvas.width() - 50) as f64, (canvas.height() / 3) as f64)?;
let modelines = [
[Reflectivity, Velocity, SpectrumWidth].as_slice(),
[DifferentialReflectivity, DifferentialPhase, CorrelationCoefficient].as_slice(),
[ClutterFilterPowerRemoved, RadarInoperative].as_slice()
];
let mut available_modes = vec![];
if let Some(ar2) = &state.ar2 {
let types = ar2.elevations.get(&state.selected_elevation).unwrap();
available_modes = types[0].available_data.keys().collect();
}
for (line_no, line) in modelines.iter().enumerate() {
let x = (canvas.width() - 50) as f64;
let y = (canvas.height() / 3) as f64 + (state.prefs.fcs * (line_no + 1)) as f64;
for (item_no, item) in line.iter().enumerate() {
let pad_start = item_no;
let pad_end = 2 - item_no;
let pad_start = " ".repeat(pad_start * 6);
let pad_end = " ".repeat(pad_end * 6);
let middle = if state.scope_mode == *item { item.selected() } else { item.unselected() };
if state.scope_mode == *item {
// display selected color
if *item == RadarInoperative {
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED));
ctx.fill_text(">RADR INOP<", x, y)?;
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
continue;
} else {
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_WHITE));
}
} else if *item == RadarInoperative {
continue;
}
if available_modes.contains(&&item.rname().to_string()) {
ctx.fill_text(&format!(" {}{}{}", pad_start, middle, pad_end), x, y)?;
}
if state.scope_mode == *item {
// reset
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
}
}
}
ctx.fill_text("ELEVATIONS", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64)?;
if let Some(ar2) = &state.ar2 {
for (line_no, (elevation_no, scans)) in ar2.elevations.iter().sorted_by(|a, b| {
Ord::cmp(&a.0, &b.0)
}).enumerate() {
if state.selected_elevation != *elevation_no {
ctx.fill_text(&format!(" {:0>3} ", elevation_no), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?;
} else {
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_WHITE));
ctx.fill_text(&format!(">{:0>3}<", elevation_no), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?;
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
}
}
} else {
ctx.fill_text("ELEVATION INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + state.prefs.fcs as f64)?;
}
// ACTUAL DATA RENDERING
if let Some(ar2) = &state.ar2 {
let px_per_km = canvas.height() / RANGE as u32;
let xc = canvas.width() / 2;
let yc = canvas.height() / 2;
}
Ok(()) Ok(())
} }

View File

@ -12,7 +12,8 @@ pub struct ScopeState {
pub prefs: Preferences, pub prefs: Preferences,
pub command_buf: String, pub command_buf: String,
pub command_buf_response_mode: bool, pub command_buf_response_mode: bool,
pub new_data_available: bool pub new_data_available: bool,
pub selected_elevation: usize
} }
pub struct RenderState { pub struct RenderState {

View File

@ -23,7 +23,8 @@ pub mod message31;
pub struct Nexrad2Chunk { pub struct Nexrad2Chunk {
pub volume_header_record: VolumeHeaderRecord, pub volume_header_record: VolumeHeaderRecord,
pub chunks: Vec<Vec<Message>>, pub chunks: Vec<Vec<Message>>,
pub meta_rda_status_data: Msg02RDAStatusData pub meta_rda_status_data: Msg02RDAStatusData,
pub elevations: HashMap<usize, Vec<Message31>>
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -130,6 +131,7 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
trace!("Loaded - {:#?}", header); trace!("Loaded - {:#?}", header);
let mut records: Vec<Vec<Message>> = vec![]; let mut records: Vec<Vec<Message>> = vec![];
let mut elevations: HashMap<usize, Vec<Message31>> = HashMap::new();
loop { loop {
// LDM records // LDM records
@ -344,17 +346,26 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
} }
} }
let message = Message::Msg31(Message31 { let message31 = Message31 {
header: header_struct, header: header_struct,
volume_info: volume_data, volume_info: volume_data,
elevation_info: elevation_data, elevation_info: elevation_data,
radial_info: radial_data, radial_info: radial_data,
available_data: data_map available_data: data_map
}); };
if let Some(elev_mut) = elevations.get_mut(&(message31.header.elevation_number as usize)) {
elev_mut.push(message31.clone());
} else {
elevations.insert(message31.header.elevation_number as usize, vec![message31.clone()]);
}
let message = Message::Msg31(message31);
trace!("{:#?}", message); trace!("{:#?}", message);
messages.push(message); messages.push(message);
continue; continue;
} }
@ -384,8 +395,6 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
trace!("{} messages loaded from chunk", messages.len()); trace!("{} messages loaded from chunk", messages.len());
records.push(messages); records.push(messages);
// break;
} }
info!("Extracting meta records"); info!("Extracting meta records");
@ -414,7 +423,8 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
Ok(Nexrad2Chunk { Ok(Nexrad2Chunk {
volume_header_record: header, volume_header_record: header,
chunks: records, chunks: records,
meta_rda_status_data: msg2 meta_rda_status_data: msg2,
elevations
}) })
} }

View File

@ -1,9 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::io::{Cursor, Read};
pub const MSG31_HEADER_LENGTH: usize = 4 + 4 + 2 + 2 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 4 + 1 + 1 + 2; pub const MSG31_HEADER_LENGTH: usize = 4 + 4 + 2 + 2 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 4 + 1 + 1 + 2;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct Message31Header { pub struct Message31Header {
pub radar_identifier: String, pub radar_identifier: String,
@ -26,7 +27,7 @@ pub struct Message31Header {
pub const VOLUME_DATA_LENGTH: usize = 1 + 3 + 2 + 1 + 1 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 2 + 2; pub const VOLUME_DATA_LENGTH: usize = 1 + 3 + 2 + 1 + 1 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 2 + 2;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct VolumeData { pub struct VolumeData {
pub datablock_type: String, pub datablock_type: String,
@ -49,7 +50,7 @@ pub struct VolumeData {
pub const ELEVATION_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4; pub const ELEVATION_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct ElevationData { pub struct ElevationData {
pub datablock_type: String, pub datablock_type: String,
@ -61,7 +62,7 @@ pub struct ElevationData {
pub const RADIAL_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4; pub const RADIAL_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct RadialData { pub struct RadialData {
pub datablock_type: String, pub datablock_type: String,
@ -78,7 +79,7 @@ pub struct RadialData {
pub const GENERIC_DATA_MOMENT_LEN: usize = 1 + 3 + 4 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 4 + 4; pub const GENERIC_DATA_MOMENT_LEN: usize = 1 + 3 + 4 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 4 + 4;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct GenericDataMoment { pub struct GenericDataMoment {
pub datablock_type: String, pub datablock_type: String,
@ -95,6 +96,7 @@ pub struct GenericDataMoment {
pub offset: f32 pub offset: f32
} }
#[derive(PartialEq, Clone)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct DataMoment { pub struct DataMoment {
pub gdm: GenericDataMoment, pub gdm: GenericDataMoment,
@ -107,7 +109,7 @@ impl Debug for DataMoment {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct Message31 { pub struct Message31 {
pub header: Message31Header, pub header: Message31Header,
@ -116,3 +118,43 @@ pub struct Message31 {
pub radial_info: Option<RadialData>, pub radial_info: Option<RadialData>,
pub available_data: HashMap<String, DataMoment> pub available_data: HashMap<String, DataMoment>
} }
impl DataMoment {
pub fn scaled_data(&self) -> Vec<f32> {
let mut gates = vec![0u16; self.gdm.data_moment_gate_count as usize];
if self.gdm.data_word_size == 8 {
for byte in &self.data {
gates.push(*byte as u16);
}
} else if self.gdm.data_word_size == 16 {
for chunk in self.data.chunks(2) {
gates.push(u16::from_be_bytes(chunk.try_into().unwrap()));
}
}
let mut scaled_data = vec![];
for gate in &gates {
if *gate == 0 {
// below threshold
scaled_data.push(999.0);
} else if *gate == 1 {
// folded
scaled_data.push(998.0);
} else {
scaled_data.push(scale_uint(*gate, self.gdm.offset, self.gdm.scale))
}
}
scaled_data
}
}
fn scale_uint(gate: u16, offset: f32, scale: f32) -> f32 {
if scale == 0.0 {
gate as f32
} else {
(gate as f32 - offset) / scale
}
}