diff --git a/Cargo.lock b/Cargo.lock index 66019a2..2957070 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1094,15 +1094,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ecolor" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" -dependencies = [ - "emath 0.29.1", -] - [[package]] name = "ecolor" version = "0.30.0" @@ -1110,7 +1101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d72e9c39f6e11a2e922d04a34ec5e7ef522ea3f5a1acfca7a19d16ad5fe50f5" dependencies = [ "bytemuck", - "emath 0.30.0", + "emath", "serde", ] @@ -1123,7 +1114,7 @@ dependencies = [ "ahash", "bytemuck", "document-features", - "egui 0.30.0", + "egui", "egui-wgpu", "egui-winit", "egui_glow", @@ -1153,18 +1144,6 @@ dependencies = [ "winit", ] -[[package]] -name = "egui" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" -dependencies = [ - "ahash", - "emath 0.29.1", - "epaint 0.29.1", - "nohash-hasher", -] - [[package]] name = "egui" version = "0.30.0" @@ -1173,8 +1152,8 @@ checksum = "252d52224d35be1535d7fd1d6139ce071fb42c9097773e79f7665604f5596b5e" dependencies = [ "accesskit", "ahash", - "emath 0.30.0", - "epaint 0.30.0", + "emath", + "epaint", "log", "nohash-hasher", "profiling", @@ -1190,7 +1169,7 @@ checksum = "ac4167fcddb279f41863074b96b877c6e48c02d543b7d6111d1f3957b9a5874f" dependencies = [ "directories", "dunce", - "egui 0.30.0", + "egui", "serde", "sysinfo 0.33.0", ] @@ -1204,8 +1183,8 @@ dependencies = [ "ahash", "bytemuck", "document-features", - "egui 0.30.0", - "epaint 0.30.0", + "egui", + "epaint", "log", "profiling", "thiserror 1.0.69", @@ -1223,7 +1202,7 @@ checksum = "1e84c2919cd9f3a38a91e8f84ac6a245c19251fd95226ed9fae61d5ea564fce3" dependencies = [ "ahash", "arboard", - "egui 0.30.0", + "egui", "log", "profiling", "raw-window-handle", @@ -1241,7 +1220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7a8198c088b1007108cb2d403bc99a5e370999b200db4f14559610d7330126" dependencies = [ "ahash", - "egui 0.30.0", + "egui", "enum-map", "log", "mime_guess2", @@ -1251,11 +1230,11 @@ dependencies = [ [[package]] name = "egui_file" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31e8280f9aea0f814013815071aebcf5c341e1d5420b381d3b0c1e3c42604d2" +checksum = "f85395d17419a23ea2d133394b3a44c50e464d50fd68b1afa36a832105d4668b" dependencies = [ - "egui 0.29.1", + "egui", ] [[package]] @@ -1266,7 +1245,7 @@ checksum = "3eaf6264cc7608e3e69a7d57a6175f438275f1b3889c1a551b418277721c95e6" dependencies = [ "ahash", "bytemuck", - "egui 0.30.0", + "egui", "glow 0.16.0", "log", "memoffset", @@ -1283,8 +1262,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c226cae80a6ee10c4d3aaf9e33bd9e9b2f1c0116b6036bdc2a1cfc9d2d0dcc10" dependencies = [ "ahash", - "egui 0.30.0", - "emath 0.30.0", + "egui", + "emath", "serde", ] @@ -1295,7 +1274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588dcf9028464fb4d23baf1f7805c13927fb540f2f9096f7d177b814848645a3" dependencies = [ "ahash", - "egui 0.30.0", + "egui", "itertools 0.13.0", "log", "serde", @@ -1307,12 +1286,6 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "emath" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" - [[package]] name = "emath" version = "0.30.0" @@ -1423,21 +1396,6 @@ dependencies = [ "log", ] -[[package]] -name = "epaint" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f" -dependencies = [ - "ab_glyph", - "ahash", - "ecolor 0.29.1", - "emath 0.29.1", - "epaint_default_fonts 0.29.1", - "nohash-hasher", - "parking_lot", -] - [[package]] name = "epaint" version = "0.30.0" @@ -1447,9 +1405,9 @@ dependencies = [ "ab_glyph", "ahash", "bytemuck", - "ecolor 0.30.0", - "emath 0.30.0", - "epaint_default_fonts 0.30.0", + "ecolor", + "emath", + "epaint_default_fonts", "log", "nohash-hasher", "parking_lot", @@ -1457,12 +1415,6 @@ dependencies = [ "serde", ] -[[package]] -name = "epaint_default_fonts" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea" - [[package]] name = "epaint_default_fonts" version = "0.30.0" @@ -4397,14 +4349,14 @@ version = "0.4.0" dependencies = [ "compute", "eframe", - "egui 0.30.0", + "egui", "egui-file-dialog", "egui_extras", "egui_file", "egui_plot", "egui_tiles", "env_logger", - "epaint 0.30.0", + "epaint", "find_peaks", "fnv", "geo", diff --git a/Cargo.toml b/Cargo.toml index a494043..bc3fd7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ serde = { version = "1.0.217", features = ["derive"] } egui_plot = {version = "0.30", features = ["serde"] } egui_tiles = "0.11" egui_extras = {version = "0.30", features = ["syntect"] } -egui_file = "0.19" +egui_file = "0.20.0" epaint = "0.30" env_logger = "0.11.6" polars = { version = "0.45.0", features = ["lazy", "parquet", "performant"] } diff --git a/src/histoer/configs.rs b/src/histoer/configs.rs index 554b536..81973ac 100644 --- a/src/histoer/configs.rs +++ b/src/histoer/configs.rs @@ -360,7 +360,7 @@ impl Configs { pub fn config_ui(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { - ui.heading("Histograms"); + ui.label("Histograms"); if ui.button("+1D").clicked() { self.configs.push(Config::Hist1D(Hist1DConfig { @@ -464,7 +464,7 @@ impl Configs { pub fn column_ui(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { - ui.heading("Column Creation"); + ui.label("Column Creation"); if ui.button("+").clicked() { self.columns.push(("".to_string(), "".to_string())); @@ -1053,30 +1053,48 @@ fn expr_from_string(expression: &str) -> Result { let mut expr_stack: Vec = Vec::new(); let mut op_stack: Vec = Vec::new(); - let mut is_first_token = true; log::debug!("Starting evaluation of expression: '{}'", expression); log::debug!("Tokens: {:?}", tokens); - for token in tokens { + let mut i = 0; + while i < tokens.len() { + let token = &tokens[i]; match token.as_str() { "+" | "-" | "*" | "/" | "**" => { + // Handle consecutive operators like "- -" or "- +" + if i < tokens.len() - 1 && (tokens[i + 1] == "-" || tokens[i + 1] == "+") { + // Collapse consecutive operators into one + let mut sign = if *token == "-" { -1.0 } else { 1.0 }; + let mut j = i + 1; + while j < tokens.len() && (tokens[j] == "-" || tokens[j] == "+") { + sign *= if tokens[j] == "-" { -1.0 } else { 1.0 }; + j += 1; + } + + if j < tokens.len() && tokens[j].parse::().is_ok() { + // Combine the collapsed operators with the number + let number = tokens[j].parse::().unwrap(); + expr_stack.push(lit(sign * number)); + i = j; // Skip to the next token after the number + continue; + } + } + + // Normal operator precedence handling while let Some(op) = op_stack.last() { - // Pop operators with higher or equal precedence - if precedence(op) > precedence(&token) - || (precedence(op) == precedence(&token) && is_left_associative(&token)) + if precedence(op) > precedence(token) + || (precedence(op) == precedence(token) && is_left_associative(token)) { apply_op(&mut expr_stack, op_stack.pop().unwrap().as_str()); } else { break; } } - op_stack.push(token); - is_first_token = false; + op_stack.push(token.clone()); } "(" => { - op_stack.push(token); - is_first_token = false; + op_stack.push(token.clone()); } ")" => { while let Some(op) = op_stack.pop() { @@ -1088,17 +1106,13 @@ fn expr_from_string(expression: &str) -> Result { } _ if token.parse::().is_ok() => { let number = token.parse::().unwrap(); - if number < 0.0 && !is_first_token { - op_stack.push("+".to_string()); - } expr_stack.push(lit(number)); - is_first_token = false; } _ => { - expr_stack.push(col(&token)); - is_first_token = false; + expr_stack.push(col(token)); } } + i += 1; } while let Some(op) = op_stack.pop() { @@ -1160,6 +1174,7 @@ fn add_computed_column( alias: &str, ) -> Result<(), PolarsError> { let computed_expr = expr_from_string(expression)?; + log::info!("Computed expression: {:?}", computed_expr); *lf = lf.clone().with_column(computed_expr.alias(alias)); // Use alias for the new column name Ok(()) } diff --git a/src/histoer/cuts.rs b/src/histoer/cuts.rs index 1985576..60cc841 100644 --- a/src/histoer/cuts.rs +++ b/src/histoer/cuts.rs @@ -2,6 +2,7 @@ use geo::Contains; use regex::Regex; use std::fs::File; use std::io::{BufReader, Write}; +use std::ops::BitAnd; use polars::prelude::*; @@ -66,6 +67,23 @@ impl Cuts { Self { cuts } } + pub fn get_active_cuts(&self) -> Cuts { + let active_cuts = self + .cuts + .iter() + .filter(|cut| match cut { + Cut::Cut1D(cut1d) => cut1d.active, + Cut::Cut2D(cut2d) => cut2d.active, + }) + .cloned() + .collect(); + Cuts::new(active_cuts) + } + + pub fn is_empty(&self) -> bool { + self.cuts.is_empty() + } + // Add a new cut pub fn add_cut(&mut self, cut: Cut) { if self.cuts.iter().any(|c| c.name() == cut.name()) { @@ -103,7 +121,7 @@ impl Cuts { pub fn ui(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { - ui.heading("Cuts"); + ui.label("Cuts"); if ui.button("+1D").clicked() { self.cuts.push(Cut::Cut1D(Cut1D::new("", ""))); @@ -182,6 +200,7 @@ impl Cuts { .column(Column::auto()) // Name .column(Column::auto()) // X Column .column(Column::auto()) // Y Column + .column(Column::auto()) // Active .column(Column::remainder()) // Actions .striped(true) .vscroll(false) @@ -220,12 +239,41 @@ impl Cuts { self.cuts.iter().all(|cut| cut.valid(df, row_idx)) } + pub fn create_combined_mask( + &self, + df: &DataFrame, + cuts: &[&Cut], + ) -> Result { + let masks: Vec = cuts + .iter() + .map(|cut| match cut { + Cut::Cut1D(cut1d) => cut1d.create_mask(df), + Cut::Cut2D(cut2d) => cut2d.create_mask(df), + }) + .collect::, _>>()?; + + // Combine all masks with a logical AND + let combined_mask = masks + .into_iter() + .reduce(|a, b| a.bitand(b)) + .unwrap_or_else(|| BooleanChunked::from_slice("".into(), &[])); + + Ok(combined_mask) + } + pub fn required_columns(&self) -> Vec { self.cuts .iter() .flat_map(|cut| cut.required_columns()) .collect() } + + pub fn generate_key(&self) -> String { + let mut cut_names: Vec = + self.cuts.iter().map(|cut| cut.name().to_string()).collect(); + cut_names.sort(); // Ensure consistent ordering + cut_names.join(",") // Create a comma-separated key + } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] pub struct Cut2D { @@ -323,6 +371,21 @@ impl Cut2D { false } + pub fn create_mask(&self, df: &DataFrame) -> Result { + let polygon = self.to_geo_polygon(); + let x_col = df.column(&self.x_column)?.f64()?; + let y_col = df.column(&self.y_column)?.f64()?; + + // Create mask by checking if each point is inside the polygon + let mask = x_col + .into_no_null_iter() + .zip(y_col.into_no_null_iter()) + .map(|(x, y)| polygon.contains(&geo::Point::new(x, y))) + .collect::(); + + Ok(mask) + } + pub fn save_cut_to_json(&self) -> Result<(), Box> { if let Some(file_path) = rfd::FileDialog::new() .add_filter("JSON Files", &["json"]) // Add a filter for json files @@ -548,4 +611,38 @@ impl Cut1D { false // Parsing failed or was not performed } } + + pub fn create_mask(&self, df: &DataFrame) -> Result { + if let Some(conditions) = &self.parsed_conditions { + let mut masks = Vec::new(); + for condition in conditions { + let column = df.column(&condition.column_name)?.f64()?; + let mask = match condition.operator.as_str() { + ">" => column.gt(condition.literal_value), + "<" => column.lt(condition.literal_value), + ">=" => column.gt_eq(condition.literal_value), + "<=" => column.lt_eq(condition.literal_value), + "==" => column.equal(condition.literal_value), + "!=" => column.not_equal(condition.literal_value), + _ => { + return Err(PolarsError::ComputeError( + format!("Unknown operator: {}", condition.operator).into(), + )) + } + }; + masks.push(mask); + } + + // Combine all masks with a logical AND + let combined_mask = masks + .into_iter() + .reduce(|a, b| a.bitand(b)) + .unwrap_or_else(|| BooleanChunked::from_slice("".into(), &[])); + Ok(combined_mask) + } else { + Err(PolarsError::ComputeError( + "Conditions not parsed for Cut1D".into(), + )) + } + } } diff --git a/src/histoer/histogrammer.rs b/src/histoer/histogrammer.rs index 15b7894..cc3d308 100644 --- a/src/histoer/histogrammer.rs +++ b/src/histoer/histogrammer.rs @@ -47,6 +47,8 @@ pub struct Histogrammer { pub calculating: Arc, // Use AtomicBool for thread-safe status tracking #[serde(skip)] pub abort_flag: Arc, // Use AtomicBool for thread-safe abort flag + #[serde(skip)] + pub progress: Arc>, pub histogram_map: HashMap, // Map full path to TabInfo } @@ -58,6 +60,7 @@ impl Default for Histogrammer { behavior: Default::default(), calculating: Arc::new(AtomicBool::new(false)), abort_flag: Arc::new(AtomicBool::new(false)), + progress: Arc::new(Mutex::new(0.0)), histogram_map: HashMap::new(), } } @@ -191,10 +194,11 @@ impl Histogrammer { &mut self, mut configs: Configs, lf: &LazyFrame, - estimated_memory: f64, // chuck size in GB + estimated_memory: f64, // chunk size in GB ) { let calculating = Arc::clone(&self.calculating); let abort_flag = Arc::clone(&self.abort_flag); + let progress = Arc::clone(&self.progress); // Set calculating to true at the start calculating.store(true, Ordering::SeqCst); @@ -246,43 +250,50 @@ impl Histogrammer { let lf = Arc::new(lf.clone().select(selected_columns.clone())); // Initialize histogram maps - let mut hist1d_map = Vec::new(); - let mut hist2d_map = Vec::new(); - - for config in &valid_configs.configs { - match config { - Config::Hist1D(hist1d) => { - if let Some((_id, egui_tiles::Tile::Pane(Pane::Histogram(hist)))) = - self.tree.tiles.iter_mut().find(|(_id, tile)| match tile { - egui_tiles::Tile::Pane(Pane::Histogram(h)) => { - h.lock().unwrap().name == hist1d.name - } - _ => false, - }) - { - hist1d_map.push((Arc::clone(hist), hist1d.clone())); - } + let hist1d_map: Vec<_> = valid_configs + .configs + .iter() + .filter_map(|config| { + if let Config::Hist1D(hist1d) = config { + self.tree.tiles.iter().find_map(|(_id, tile)| match tile { + egui_tiles::Tile::Pane(Pane::Histogram(hist)) + if hist.lock().unwrap().name == hist1d.name => + { + Some((Arc::clone(hist), hist1d.clone())) + } + _ => None, + }) + } else { + None } - Config::Hist2D(hist2d) => { - if let Some((_id, egui_tiles::Tile::Pane(Pane::Histogram2D(hist)))) = - self.tree.tiles.iter_mut().find(|(_id, tile)| match tile { - egui_tiles::Tile::Pane(Pane::Histogram2D(h)) => { - h.lock().unwrap().name == hist2d.name - } - _ => false, - }) - { - hist2d_map.push((Arc::clone(hist), hist2d.clone())); - } + }) + .collect(); + + let hist2d_map: Vec<_> = valid_configs + .configs + .iter() + .filter_map(|config| { + if let Config::Hist2D(hist2d) = config { + self.tree.tiles.iter().find_map(|(_id, tile)| match tile { + egui_tiles::Tile::Pane(Pane::Histogram2D(hist)) + if hist.lock().unwrap().name == hist2d.name => + { + Some((Arc::clone(hist), hist2d.clone())) + } + _ => None, + }) + } else { + None } - } - } + }) + .collect(); // Spawn the batch processing task asynchronously rayon::spawn({ let calculating = Arc::clone(&calculating); let lf = Arc::clone(&lf); // Clone lf to move into the spawn closure let progress_bar = progress_bar.clone(); + let total_rows = row_count as f32; move || { let mut row_start = 0; @@ -305,138 +316,62 @@ impl Histogrammer { if let Ok(df) = batch_lf.collect() { let height = df.height(); - // Cache bin values for 1D histograms - let cached_bins_1d: Vec> = hist1d_map - .par_iter() - .map(|(_, meta)| { - if let Ok(col) = df.column(&meta.column_name).and_then(|c| c.f64()) - { - col.into_no_null_iter() - .enumerate() - .filter_map(|(index, value)| { - if value != -1e6 && meta.cuts.valid(&df, index) { - Some(value) - } else { - None - } - }) - .collect() - } else { - Vec::new() - } - }) - .collect(); - - // Cache bin values for 2D histograms - let cached_bins_2d: Vec> = hist2d_map - .par_iter() - .map(|(_, meta)| { - if let (Ok(x_col), Ok(y_col)) = ( - df.column(&meta.x_column_name).and_then(|c| c.f64()), - df.column(&meta.y_column_name).and_then(|c| c.f64()), - ) { - x_col - .into_no_null_iter() - .zip(y_col.into_no_null_iter()) - .enumerate() - .filter_map(|(index, (x, y))| { - if x != -1e6 && y != -1e6 && meta.cuts.valid(&df, index) - { - Some((x, y)) - } else { - None - } - }) - .collect() - } else { - Vec::new() - } - }) - .collect(); - - // fill nested vector columns - let cached_nested_bins_2d: Vec> = hist2d_map - .par_iter() - .map(|(_, meta)| { - if let (Ok(x_col), Ok(y_col)) = ( - df.column(&meta.x_column_name), - df.column(&meta.y_column_name), - ) { - if let (Ok(x_list), Ok(y_list)) = (x_col.list(), y_col.list()) { - x_list - .into_iter() - .zip(y_list) - .enumerate() - .flat_map(|(index, (x_row, y_row))| { - if let (Some(x_values), Some(y_values)) = - (x_row, y_row) - { - let x_values = x_values.f64().unwrap(); - let y_values = y_values.f64().unwrap(); - - x_values - .into_no_null_iter() - .zip(y_values.into_no_null_iter()) - .filter_map(|(x, y)| { - if x != -1e6 - && y != -1e6 - && meta.cuts.valid(&df, index) - { - Some((x, y)) - } else { - None - } - }) - .collect::>() - } else { - Vec::new() - } - }) - .collect() - } else { - Vec::new() - } - } else { - Vec::new() - } - }) - .collect(); - - // Fill 1D histograms - hist1d_map - .par_iter() - .zip(cached_bins_1d) - .for_each(|((hist, _), bins)| { + // Fill 1D histograms in parallel + hist1d_map.par_iter().for_each(|(hist, meta)| { + if let Ok(column) = df.column(&meta.column_name).and_then(|c| c.f64()) { let mut hist = hist.lock().unwrap(); - bins.into_iter().for_each(|value| hist.fill(value)); - hist.plot_settings.egui_settings.reset_axis = true; - }); - - // Fill 2D histograms - hist2d_map.par_iter().zip(cached_bins_2d).for_each( - |((hist, meta), bins)| { - let mut hist = hist.lock().unwrap(); - bins.into_iter().for_each(|(x, y)| hist.fill(x, y)); - hist.plot_settings.x_column = meta.x_column_name.clone(); - hist.plot_settings.y_column = meta.y_column_name.clone(); - hist.plot_settings.recalculate_image = true; - }, - ); - - // Fill nested 2D histograms - hist2d_map.par_iter().zip(cached_nested_bins_2d).for_each( - |((hist, _), bins)| { + column.into_no_null_iter().enumerate().for_each( + |(index, value)| { + if value != -1e6 && meta.cuts.valid(&df, index) { + hist.fill(value); + hist.plot_settings.egui_settings.reset_axis = true; + } + }, + ); + } + }); + + // Fill 2D histograms in parallel + hist2d_map.par_iter().for_each(|(hist, meta)| { + if let (Ok(x_col), Ok(y_col)) = ( + df.column(&meta.x_column_name).and_then(|c| c.f64()), + df.column(&meta.y_column_name).and_then(|c| c.f64()), + ) { let mut hist = hist.lock().unwrap(); - bins.into_iter().for_each(|(x, y)| hist.fill(x, y)); - }, - ); + x_col + .into_no_null_iter() + .zip(y_col.into_no_null_iter()) + .enumerate() + .for_each(|(index, (x, y))| { + if x != -1e6 && y != -1e6 && meta.cuts.valid(&df, index) { + hist.fill(x, y); + } + }); + } + }); + + hist2d_map.par_iter().for_each(|(hist, _)| { + let mut hist = hist.lock().unwrap(); + hist.plot_settings.recalculate_image = true; + }); progress_bar.inc(height as u64); + + // Update progress as a percentage + let completed_rows = row_start as f32 + height as f32; + let percentage = completed_rows / total_rows; + { + let mut progress_lock = progress.lock().unwrap(); + *progress_lock = percentage; + } } row_start += rows_per_chunk; } + let mut progress_lock = progress.lock().unwrap(); + *progress_lock = 1.0; + progress_bar.finish_with_message("Processing complete."); // Set calculating to false when processing is complete calculating.store(false, Ordering::SeqCst); @@ -546,6 +481,8 @@ impl Histogrammer { self.reorganize(); } + ui.separator(); + if ui.button("Reset").clicked() { *self = Default::default(); } diff --git a/src/histogram_scripter/custom_scripts.rs b/src/histogram_scripter/custom_scripts.rs index 06043ef..cc4a381 100644 --- a/src/histogram_scripter/custom_scripts.rs +++ b/src/histogram_scripter/custom_scripts.rs @@ -1,18 +1,21 @@ use crate::histoer::{ - configs::Configs, + configs::{Config, Configs}, cuts::{Cut, Cuts}, }; +use egui_extras::{Column, TableBuilder}; use std::f64::consts::PI; #[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct CustomConfigs { pub sps: SPSConfig, + pub cebra: CeBrAConfig, } impl Default for CustomConfigs { fn default() -> Self { Self { sps: SPSConfig::new(), + cebra: CeBrAConfig::default(), } } } @@ -22,16 +25,31 @@ impl CustomConfigs { ui.horizontal(|ui| { ui.label("Custom: "); ui.checkbox(&mut self.sps.active, "SPS"); + ui.checkbox(&mut self.cebra.active, "CeBrA"); }); if self.sps.active { - ui.horizontal(|ui| { - ui.collapsing("SE-SPS", |ui| { + ui.collapsing("SE-SPS", |ui| { + self.sps.ui(ui); + ui.horizontal(|ui| { + ui.add_space(ui.available_width() - 40.0); if ui.button("Reset").clicked() { self.sps = SPSConfig::new(); self.sps.active = true; } - self.sps.ui(ui); + }); + }); + } + + if self.cebra.active { + ui.collapsing("CeBrA", |ui| { + self.cebra.ui(ui); + ui.horizontal(|ui| { + ui.add_space(ui.available_width() - 40.0); + if ui.button("Reset").clicked() { + self.cebra = CeBrAConfig::default(); + self.cebra.active = true; + } }); }); } @@ -41,7 +59,15 @@ impl CustomConfigs { let mut configs = Configs::default(); if self.sps.active { - configs.merge(self.sps.configs.clone()); // Ensure `merge` handles in-place modifications + // get the updated configs from sps + let sps_configs = self.sps.update_configs_with_cuts(); + configs.merge(sps_configs.clone()); // Ensure `merge` handles in-place modifications + } + + if self.cebra.active { + // get the updated configs from cebra + let cebra_configs = self.cebra.get_configs(); + configs.merge(cebra_configs.clone()); // Ensure `merge` handles in-place modifications } configs @@ -50,7 +76,6 @@ impl CustomConfigs { #[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct Calibration { - pub name: String, pub a: f64, pub b: f64, pub c: f64, @@ -62,7 +87,6 @@ pub struct Calibration { impl Calibration { pub fn ui(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { - ui.label(self.name.clone()); if self.active { ui.add(egui::DragValue::new(&mut self.a).speed(0.01).prefix("a: ")); ui.add(egui::DragValue::new(&mut self.b).speed(0.01).prefix("b: ")); @@ -94,15 +118,525 @@ impl Calibration { ui.checkbox(&mut self.active, "Active"); }); } + + pub fn new_column(&self, column: &str, alias: &str) -> (String, String) { + ( + format!( + "({})*{}**2 + ({})*{} + ({})", + self.a, column, self.b, column, self.c + ), + alias.to_string(), + ) + } +} + +/*************************** CeBrA Custom Struct ***************************/ + +#[derive(Clone, serde::Deserialize, serde::Serialize)] +pub struct TimeCut { + pub mean: f64, + pub low: f64, + pub high: f64, + pub active: bool, +} + +impl TimeCut { + pub fn ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.label("Time Cut: "); + if self.active { + ui.add( + egui::DragValue::new(&mut self.mean) + .speed(0.01) + .prefix("Mean: "), + ); + ui.add( + egui::DragValue::new(&mut self.low) + .speed(0.01) + .prefix("Low: "), + ); + ui.add( + egui::DragValue::new(&mut self.high) + .speed(0.01) + .prefix("High: "), + ); + } + ui.checkbox(&mut self.active, "Active"); + }); + } +} + +#[derive(Clone, serde::Deserialize, serde::Serialize)] +pub struct Cebr3 { + pub number: usize, + pub sps: bool, + pub timecut: TimeCut, + pub gainmatch: Calibration, + pub energy_calibration: Calibration, +} + +impl Cebr3 { + pub fn new(number: usize, sps: bool) -> Self { + Self { + number, + sps, + timecut: TimeCut { + mean: 0.0, + low: -3000.0, + high: 3000.0, + active: sps, + }, + gainmatch: Calibration { + a: 0.0, + b: 1.0, + c: 0.0, + bins: 512, + range: (0.0, 4096.0), + active: false, + }, + energy_calibration: Calibration { + a: 0.0, + b: 1.0, + c: 0.0, + bins: 512, + range: (0.0, 4096.0), + active: false, + }, + } + } + + #[rustfmt::skip] + #[allow(clippy::all)] + pub fn config(&self) -> Configs { + + let mut configs = Configs::default(); + + let range = (0.0, 4096.0); + let bins = 512; + + configs.hist1d(&format!("CeBrA/Cebra{}/Cebra{}Energy", self.number, self.number), &format!("Cebra{}Energy", self.number), range, bins, None); + + + if self.gainmatch.active { + configs.columns.push(self.gainmatch.new_column(&format!("Cebra{}Energy", self.number),&format!("Cebra{}GainMatched", self.number))); + configs.hist1d(&format!("CeBrA/Cebra{}/Cebra{} Gain Matched", self.number, self.number), &format!("Cebra{}GainMatched", self.number), self.gainmatch.range, self.gainmatch.bins, None); + configs.hist1d(&"CeBrA/Gain Matched", &format!("Cebra{}GainMatched", self.number), self.gainmatch.range, self.gainmatch.bins, None); + } + + if self.energy_calibration.active { + if self.gainmatch.active { + configs.columns.push(self.energy_calibration.new_column(&format!("Cebra{}GainMatched", self.number),&format!("Cebra{}EnergyCalibrated", self.number))); + } else { + configs.columns.push(self.energy_calibration.new_column(&format!("Cebra{}Energy", self.number),&format!("Cebra{}EnergyCalibrated", self.number))); + } + configs.hist1d(&format!("CeBrA/Cebra{}/Cebra{} Energy Calibrated", self.number, self.number), &format!("Cebra{}EnergyCalibrated", self.number), self.energy_calibration.range, self.energy_calibration.bins, None); + configs.hist1d(&"CeBrA/Energy Calibrated", &format!("Cebra{}EnergyCalibrated", self.number), self.energy_calibration.range, self.energy_calibration.bins, None); + } + + if self.sps { + configs.hist1d(&format!("CeBrA/Cebra{}/Cebra{}RelTime", self.number, self.number), &format!("Cebra{}RelTime", self.number), (-3200.0, 3200.0), 6400, None); + configs.hist2d(&format!("CeBrA/Cebra{}/Cebra{}Energy v Xavg", self.number, self.number), &format!("Xavg"), &format!("Cebra{}Energy", self.number), (-300.0, 300.0), (0.0, 4096.0), (600, 512), None); + configs.hist2d(&format!("CeBrA/Cebra{}/Cebra{}RelTime v Xavg", self.number, self.number), &format!("Xavg"), &format!("Cebra{}RelTime", self.number), (-300.0, 300.0), (-3200.0, 3200.0), (600, 6400), None); + configs.hist2d(&format!("CeBrA/Cebra{}/Theta v Cebra{}RelTime", self.number, self.number), &format!("Cebra{}RelTime", self.number), "Theta", (-3200.0, 3200.0), (0.0, PI), (6400, 300), None); + + if self.timecut.active { + // columns with 2 minus do not work + configs.columns.push((format!("Cebra{}RelTime - {}", self.number, self.timecut.mean), format!("Cebra{}RelTimeShifted", self.number))); + configs.hist1d(&format!("CeBrA/Cebra{}/Cebra{}RelTimeShifted", self.number, self.number), &format!("Cebra{}RelTimeShifted", self.number), (-3200.0, 3200.0), 6400, None); + + + let cebra_time_cut = Cut::new_1d(&format!("Cebra{} Time Cut", self.number), &format!("Cebra{}RelTime >= {} && Cebra{}RelTime <= {}", self.number, self.timecut.low, self.number, self.timecut.high)); + configs.cuts.add_cut(cebra_time_cut.clone()); + let tcut = Some(Cuts::new(vec![cebra_time_cut.clone()])); + + configs.hist1d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{}RelTime", self.number, self.number), &format!("Cebra{}RelTime", self.number), (-3200.0, 3200.0), 6400, tcut.clone()); + configs.hist1d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{}RelTimeShifted", self.number, self.number), &format!("Cebra{}RelTimeShifted", self.number), (-50.0, 50.0), 100, tcut.clone()); + + configs.hist1d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{}Energy", self.number, self.number), &format!("Cebra{}Energy", self.number), range, bins, tcut.clone()); + configs.hist2d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{}Energy v Xavg", self.number, self.number), &format!("Xavg"), &format!("Cebra{}Energy", self.number), (-300.0, 300.0), (0.0, 4096.0), (600, 512), tcut.clone()); + configs.hist2d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{}Energy v X1", self.number, self.number), &format!("X1"), &format!("Cebra{}Energy", self.number), (-300.0, 300.0), (0.0, 4096.0), (600, 512), tcut.clone()); + + if self.gainmatch.active { + configs.hist1d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{} Gain Matched", self.number, self.number), &format!("Cebra{}GainMatched", self.number), self.gainmatch.range, self.gainmatch.bins, tcut.clone()); + configs.hist2d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{} Gain Matched v Xavg", self.number, self.number), &format!("Xavg"), &format!("Cebra{}GainMatched", self.number), (-300.0, 300.0), self.gainmatch.range, (600, self.gainmatch.bins), tcut.clone()); + configs.hist2d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{} Gain Matched v X1", self.number, self.number), &format!("X1"), &format!("Cebra{}GainMatched", self.number), (-300.0, 300.0), self.gainmatch.range, (600, self.gainmatch.bins), tcut.clone()); + + configs.hist2d(&format!("CeBrA/Time Cut/CeBrA Gain Matched v Xavg"), &format!("Xavg"), &format!("Cebra{}Energy", self.number), (-300.0, 300.0), self.gainmatch.range, (600, self.gainmatch.bins), tcut.clone()); + configs.hist2d(&format!("CeBrA/Time Cut/CeBrA Gain Matched v X1"), &format!("X1"), &format!("Cebra{}Energy", self.number), (-300.0, 300.0), self.gainmatch.range, (600, self.gainmatch.bins), tcut.clone()); + } + if self.energy_calibration.active { + configs.hist1d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{} Energy Calibrated", self.number, self.number), &format!("Cebra{}EnergyCalibrated", self.number), self.energy_calibration.range, self.energy_calibration.bins, tcut.clone()); + configs.hist2d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{} Energy Calibrated v Xavg", self.number, self.number), &format!("Xavg"), &format!("Cebra{}EnergyCalibrated", self.number), (-300.0, 300.0), self.energy_calibration.range, (600, self.energy_calibration.bins), tcut.clone()); + configs.hist2d(&format!("CeBrA/Cebra{}/Time Cut/Cebra{} Energy Calibrated v X1", self.number, self.number), &format!("X1"), &format!("Cebra{}EnergyCalibrated", self.number), (-300.0, 300.0), self.energy_calibration.range, (600, self.energy_calibration.bins), tcut.clone()); + + configs.hist2d(&format!("CeBrA/Time Cut/CeBrA Energy Calibrated v Xavg"), &format!("Xavg"), &format!("Cebra{}EnergyCalibrated", self.number), (-300.0, 300.0), self.energy_calibration.range, (600, self.energy_calibration.bins), tcut.clone()); + configs.hist2d(&format!("CeBrA/Time Cut/CeBrA Energy Calibrated v X1"), &format!("X1"), &format!("Cebra{}EnergyCalibrated", self.number), (-300.0, 300.0), self.energy_calibration.range, (600, self.energy_calibration.bins), tcut.clone()); + } + } + } + + configs + } +} + +#[derive(Clone, Default, serde::Deserialize, serde::Serialize)] +pub struct CeBrAConfig { + pub active: bool, + pub detectors: Vec, + pub sps: bool, } +impl CeBrAConfig { + pub fn add_detector(&mut self, number: usize) { + self.detectors.push(Cebr3::new(number, self.sps)); + } + + pub fn time_cut_ui(&mut self, ui: &mut egui::Ui) { + let mut indices_to_remove = Vec::new(); + + ui.label("Time Cuts"); + // Create the table + TableBuilder::new(ui) + .id_salt("cebra_timecuts") // Unique identifier for the table + .column(Column::auto()) // Detector Number + .column(Column::auto()) // Mean + .column(Column::auto()) // Low + .column(Column::auto()) // High + .column(Column::auto()) // Active + .column(Column::remainder()) // Actions (Remove) + .striped(true) // Optional for better readability + .vscroll(false) // Disable vertical scrolling for compact tables + .header(20.0, |mut header| { + header.col(|ui| { + ui.label("Detector"); + }); + header.col(|ui| { + ui.label("Mean (ns)"); + }); + header.col(|ui| { + ui.label("Low (ns)"); + }); + header.col(|ui| { + ui.label("High (ns)"); + }); + header.col(|ui| { + ui.label("Active"); + }); + header.col(|ui| { + ui.label("Actions"); + }); + }) + .body(|mut body| { + for (index, detector) in self.detectors.iter_mut().enumerate() { + body.row(18.0, |mut row| { + // Detector Number + row.col(|ui| { + ui.label(format!("#{}", detector.number)); + }); + + // Mean + row.col(|ui| { + ui.add( + egui::DragValue::new(&mut detector.timecut.mean) + .speed(0.01) + .prefix(" "), + ); + }); + + // Low + row.col(|ui| { + ui.add( + egui::DragValue::new(&mut detector.timecut.low) + .speed(0.01) + .prefix(" "), + ); + }); + + // High + row.col(|ui| { + ui.add( + egui::DragValue::new(&mut detector.timecut.high) + .speed(0.01) + .prefix(" "), + ); + }); + + // Active + row.col(|ui| { + ui.checkbox(&mut detector.timecut.active, ""); + }); + + // Actions + row.col(|ui| { + if ui.button("Remove").clicked() { + indices_to_remove.push(index); + } + }); + }); + } + }); + + // Remove detectors marked for deletion + for &index in indices_to_remove.iter().rev() { + self.detectors.remove(index); + } + } + + pub fn gain_matching_ui(&mut self, ui: &mut egui::Ui) { + // Temporarily store the range and bins to avoid conflicting borrows + let (common_range, common_bins) = if let Some(first_detector) = self.detectors.get_mut(0) { + ui.separator(); + + let mut range = first_detector.gainmatch.range; + let mut bins = first_detector.gainmatch.bins; + + ui.horizontal(|ui| { + ui.label("Gain Matching"); + + ui.separator(); + + // Common Range + ui.label("Range:"); + ui.add(egui::DragValue::new(&mut range.0).speed(1.0).prefix("(")); + ui.add(egui::DragValue::new(&mut range.1).speed(1.0).suffix(")")); + + // Common Bins + ui.label("Bins:"); + ui.add(egui::DragValue::new(&mut bins).speed(1).prefix("Bins: ")); + }); + + // Update the first detector with the new range and bins + first_detector.gainmatch.range = range; + first_detector.gainmatch.bins = bins; + + (range, bins) + } else { + return; // No detectors to configure + }; + + // Update all other detectors with the common range and bins + for detector in &mut self.detectors[1..] { + detector.gainmatch.range = common_range; + detector.gainmatch.bins = common_bins; + } + + TableBuilder::new(ui) + .id_salt("cebra_gain_matching") // Unique identifier for the table + .column(Column::auto()) // Detector Number + .column(Column::auto()) // Coefficient A + .column(Column::auto()) // Coefficient B + .column(Column::auto()) // Coefficient C + .column(Column::remainder()) // Active + .striped(true) // Optional for better readability + .vscroll(false) // Disable vertical scrolling for compact tables + .header(20.0, |mut header| { + header.col(|ui| { + ui.label("Detector"); + }); + header.col(|ui| { + ui.label("A"); + }); + header.col(|ui| { + ui.label("B"); + }); + header.col(|ui| { + ui.label("C"); + }); + header.col(|ui| { + ui.label("Active"); + }); + }) + .body(|mut body| { + for detector in &mut self.detectors { + body.row(18.0, |mut row| { + // Detector Number + row.col(|ui| { + ui.label(format!("#{}", detector.number)); + }); + + // Coefficient A + row.col(|ui| { + ui.add_enabled( + detector.gainmatch.active, + egui::DragValue::new(&mut detector.gainmatch.a) + .speed(0.01) + .prefix("A: "), + ); + }); + + // Coefficient B + row.col(|ui| { + ui.add_enabled( + detector.gainmatch.active, + egui::DragValue::new(&mut detector.gainmatch.b) + .speed(0.01) + .prefix("B: "), + ); + }); + + // Coefficient C + row.col(|ui| { + ui.add_enabled( + detector.gainmatch.active, + egui::DragValue::new(&mut detector.gainmatch.c) + .speed(0.01) + .prefix("C: "), + ); + }); + + // Active + row.col(|ui| { + ui.checkbox(&mut detector.gainmatch.active, ""); + }); + }); + } + }); + } + + pub fn energy_calibration_ui(&mut self, ui: &mut egui::Ui) { + // Temporarily store the range and bins to avoid conflicting borrows + let (common_range, common_bins) = if let Some(first_detector) = self.detectors.get_mut(0) { + ui.separator(); + + let mut range = first_detector.energy_calibration.range; + let mut bins = first_detector.energy_calibration.bins; + + ui.horizontal(|ui| { + ui.label("Energy Calibration"); + + ui.separator(); + + // Common Range + ui.label("Range:"); + ui.add(egui::DragValue::new(&mut range.0).speed(1.0).prefix("(")); + ui.add(egui::DragValue::new(&mut range.1).speed(1.0).suffix(")")); + + // Common Bins + ui.label("Bins:"); + ui.add(egui::DragValue::new(&mut bins).speed(1).prefix("Bins: ")); + }); + + // Update the first detector with the new range and bins + first_detector.energy_calibration.range = range; + first_detector.energy_calibration.bins = bins; + + (range, bins) + } else { + return; // No detectors to configure + }; + + // Update all other detectors with the common range and bins + for detector in &mut self.detectors[1..] { + detector.energy_calibration.range = common_range; + detector.energy_calibration.bins = common_bins; + } + + TableBuilder::new(ui) + .id_salt("cebra_energy_calibration") // Unique identifier for the table + .column(Column::auto()) // Detector Number + .column(Column::auto()) // Coefficient A + .column(Column::auto()) // Coefficient B + .column(Column::auto()) // Coefficient C + .column(Column::remainder()) // Active + .striped(true) // Optional for better readability + .vscroll(false) // Disable vertical scrolling for compact tables + .header(20.0, |mut header| { + header.col(|ui| { + ui.label("Detector"); + }); + header.col(|ui| { + ui.label("A"); + }); + header.col(|ui| { + ui.label("B"); + }); + header.col(|ui| { + ui.label("C"); + }); + header.col(|ui| { + ui.label("Active"); + }); + }) + .body(|mut body| { + for detector in &mut self.detectors { + body.row(18.0, |mut row| { + // Detector Number + row.col(|ui| { + ui.label(format!("#{}", detector.number)); + }); + + // Coefficient A + row.col(|ui| { + ui.add_enabled( + detector.energy_calibration.active, + egui::DragValue::new(&mut detector.energy_calibration.a) + .speed(0.01) + .prefix("A: "), + ); + }); + + // Coefficient B + row.col(|ui| { + ui.add_enabled( + detector.energy_calibration.active, + egui::DragValue::new(&mut detector.energy_calibration.b) + .speed(0.01) + .prefix("B: "), + ); + }); + + // Coefficient C + row.col(|ui| { + ui.add_enabled( + detector.energy_calibration.active, + egui::DragValue::new(&mut detector.energy_calibration.c) + .speed(0.01) + .prefix("C: "), + ); + }); + + // Active + row.col(|ui| { + ui.checkbox(&mut detector.energy_calibration.active, ""); + }); + }); + } + }); + } + + pub fn ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + if ui.button("Add Detector").clicked() { + self.add_detector(self.detectors.len()); + } + ui.checkbox(&mut self.sps, "SE-SPS"); + }); + + self.time_cut_ui(ui); + self.gain_matching_ui(ui); + self.energy_calibration_ui(ui); + + for detector in &mut self.detectors { + detector.sps = self.sps; + } + } + + pub fn get_configs(&self) -> Configs { + let mut configs = Configs::default(); + + for detector in &self.detectors { + configs.merge(detector.config()); + } + + configs + } +} /*************************** SE-SPS Custom Struct ***************************/ #[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct SPSConfig { active: bool, xavg: Calibration, cuts: Cuts, - configs: Configs, } impl Default for SPSConfig { @@ -110,7 +644,6 @@ impl Default for SPSConfig { Self { active: false, xavg: Calibration { - name: "Xavg Energy Calibration:".into(), a: 0.0, b: 1.0, c: 0.0, @@ -119,36 +652,25 @@ impl Default for SPSConfig { active: false, }, cuts: Cuts::default(), - configs: Configs::default(), } } } impl SPSConfig { pub fn new() -> Self { - let mut sps = SPSConfig::default(); - sps.configs = sps.sps_configs(); - sps + Self::default() } pub fn ui(&mut self, ui: &mut egui::Ui) { ui.separator(); - ui.heading("Calibration"); + ui.label("Calibration"); self.xavg.ui(ui); ui.separator(); self.cuts.ui(ui); - - ui.separator(); - - egui::CollapsingHeader::new("Script") - .default_open(false) - .show(ui, |ui| { - self.configs.ui(ui); - }); } #[rustfmt::skip] @@ -177,10 +699,7 @@ impl SPSConfig { configs.columns.push(("ScintRightTime - ScintLeftTime".into(), "ScintRightTime_ScintLeftTime".into())); if self.xavg.active { - configs.columns.push(( - format!("({})*Xavg*Xavg + ({})*Xavg + ({})", self.xavg.a, self.xavg.b, self.xavg.c), - "XavgEnergyCalibrated".into(), - )); + configs.columns.push(self.xavg.new_column("Xavg", "XavgEnergyCalibrated")); } let mut cuts = Cuts::default(); @@ -208,7 +727,7 @@ impl SPSConfig { } configs.hist2d("SE-SPS/Focal Plane/X2 v X1", "X1", "X2", fp_range, fp_range, (fp_bins, fp_bins), None); configs.hist2d("SE-SPS/Focal Plane/Theta v Xavg", "Xavg", "Theta", fp_range, (0.0, PI), (fp_bins, fp_bins), None); - configs.hist2d("SE-SPS/Focal Plane/Rays", "X", "Z", fp_range, (-50.0, 50.0), (fp_bins, 100), None); + // configs.hist2d("SE-SPS/Focal Plane/Rays", "X", "Z", fp_range, (-50.0, 50.0), (fp_bins, 100), None); let cut_bothplanes = Some(Cuts::new(vec![bothplanes_cut.clone()])); let cut_only_x1_plane = Some(Cuts::new(vec![only_x1_plane_cut])); @@ -315,260 +834,81 @@ impl SPSConfig { configs } - // fn update_configs_with_cuts(&self) -> Configs { - // } -} - -/*************************** CeBrA Cutsom Struct ***************************/ + pub fn update_configs_with_cuts(&self) -> Configs { + // Get the active cuts + let active_cuts = self.cuts.get_active_cuts(); -pub struct TimeCut { - pub mean: f64, - pub low: f64, - pub high: f64, - pub active: bool, -} - -impl TimeCut { - pub fn ui(&mut self, ui: &mut egui::Ui) { - ui.horizontal(|ui| { - ui.label("Time Cut: "); - if self.active { - ui.add( - egui::DragValue::new(&mut self.mean) - .speed(0.01) - .prefix("Mean: "), - ); - ui.add( - egui::DragValue::new(&mut self.low) - .speed(0.01) - .prefix("Low: "), - ); - ui.add( - egui::DragValue::new(&mut self.high) - .speed(0.01) - .prefix("High: "), - ); - } - ui.checkbox(&mut self.active, "Active"); - }); - } -} - -pub struct Cebr3 { - pub detector_number: usize, - pub sps: bool, - pub timecut: TimeCut, - pub gainmatch: Calibration, - pub energy_calibration: Calibration, -} - -// impl Cebr3 { -// pub fn new(detector_number: usize, sps: bool) -> Self { -// Self { -// detector_number, -// sps, -// timecut: TimeCut { -// mean: 0.0, -// low: -3000.0, -// high: 3000.0, -// active: sps, -// }, -// gainmatch: Calibration { -// name: format!("CeBr{} Gain Match:", detector_number), -// a: 0.0, -// b: 1.0, -// c: 0.0, -// bins: 512, -// range: (0.0, 4096.0), -// active: false, -// }, -// energy_calibration: Calibration { -// name: format!("CeBr{} Energy Calibration:", detector_number), -// a: 0.0, -// b: 1.0, -// c: 0.0, -// bins: 512, -// range: (0.0, 4096.0), -// active: false, -// }, -// } -// } - -// pub fn calibration_ui(&mut self, ui: &mut egui::Ui) { -// ui.vertical(|ui| { -// self.timecut.ui(ui); -// self.gainmatch.ui(ui); -// self.energy_calibration.ui(ui); -// }); -// } - -// pub fn config(&self) -> Configs { - -// let mut configs = Configs::default(); - -// configs -// } -// } - -/* - - - -pub struct EnergyCalibration { - pub a: f64, - pub b: f64, - pub c: f64, - pub bins: usize, - pub range: (f64, f64), -} - -#[rustfmt::skip] -#[allow(clippy::all)] -pub fn cebra(h: &mut Histogrammer, lf: LazyFrame, detector_number: usize, timecut: Option, gainmatch: Option, energy_calibration: Option) { - - let i = detector_number; - let lf_cebra = lf.filter(col(&format!("Cebra{}Energy", detector_number)).neq(lit(-1e6))); - - let mut column_names = get_column_names_from_lazyframe(&lf_cebra); - - // add the psd column if it doesn't exist - let lf_cebra = lf_cebra.with_column( - ( ( col(&format!("Cebra{}Energy", i)) - col(&format!("Cebra{}Short", i)) )/ col(&format!("Cebra{}Energy", i) ) ).alias(&format!("Cebra{}PSD", i)) - ); - - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/Cebra{}Energy", i, i), &lf_cebra, &format!("Cebra{}Energy", i), 512, range); - h.add_fill_hist2d(&format!("CeBrA/Cebra{}/PSD v Energy", i), &lf_cebra, &format!("Cebra{}Energy", i), &format!("Cebra{}PSD", i), (512, 512), (range, (0.0, 1.0))); - - - /* - Gain Matching Values - - If the gain match column already exists, skip gain matching. - This is done so if you gain match on a run by run basis, you don't need to the gain match values. - Just make sure you the alias is the same when analyzing the data externally. - */ - let lf_cebra = if column_names.contains(&format!("Cebra{}EnergyGM", i)) { - log::warn!("Gain matched energy column already exists, skipping gain matching"); - lf_cebra - } else { - if let Some(ref gainmatch) = gainmatch { - log::info!("Gain matching Cebra{}Energy", i); - let lf = lf_cebra.with_column( - (lit(gainmatch.a) * col(&format!("Cebra{}Energy", i)).pow(2.0) - + lit(gainmatch.b) * col(&format!("Cebra{}Energy", i)) - + lit(gainmatch.c)).alias(&format!("Cebra{}EnergyGM", i)) - ); - - // update column names to include the gain matched column - column_names.push(format!("Cebra{}EnergyGM", i)); - - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/Cebra{}EnergyGM", i, i), &lf, &format!("Cebra{}EnergyGM", i), 512, range); - - lf - } else{ - lf_cebra - } - }; - - // Energy Calibration logic -> if energy calibration is provided, apply it to the gain matched energy column if it exists, otherwise apply it to the raw energy column - let lf_cebra = if let Some(ref ecal) = energy_calibration { - let lf = if column_names.contains(&format!("Cebra{}EnergyGM", i)) { - lf_cebra.with_column( - (lit(ecal.a) * col(&format!("Cebra{}EnergyGM", i)).pow(2.0) - + lit(ecal.b) * col(&format!("Cebra{}EnergyGM", i)) - + lit(ecal.c)).alias(&format!("Cebra{}EnergyCalibrated", i)) - ) - } else { - lf_cebra.with_column( - (lit(ecal.a) * col(&format!("Cebra{}Energy", i)).pow(2.0) - + lit(ecal.b) * col(&format!("Cebra{}Energy", i)) - + lit(ecal.c)).alias(&format!("Cebra{}EnergyCalibrated", i)) - ) - }; - - // Update column names to include the calibrated energy column - column_names.push(format!("Cebra{}EnergyCalibrated", i)); - - // Fill histogram for the calibrated energy - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/Cebra{}EnergyCalibrated", i, i), &lf, &format!("Cebra{}EnergyCalibrated", i), ecal.bins, ecal.range); - - lf - } else { - lf_cebra - }; - - - let mut sps = false; - - // Check if ScintLeftTime exists and therefore SPS is present - let lf_cebra = if column_names.contains(&"ScintLeftTime".to_string()) { - sps = true; - - // Check if Cebra#RelTime exists, if not, create it as Cebra#Time - ScintLeftTime - if !column_names.contains(&format!("Cebra{}RelTime", i)) { - column_names.push(format!("Cebra{}RelTime", i)); - lf_cebra.with_column( - (col(&format!("Cebra{}Time", i)) - col("ScintLeftTime")).alias(&format!("Cebra{}RelTime", i)) - ) + // If there are no cuts, return the sps configs + if active_cuts.is_empty() { + self.sps_configs() } else { - lf_cebra - } - } else { - lf_cebra - }; - - if sps { - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/Cebra{}RelTime", i, i), &lf_cebra, &format!("Cebra{}RelTime", i), 6400, (-3200.0, 3200.0)); - h.add_fill_hist2d(&format!("CeBrA/Cebra{}/Cebra{}Energy v Xavg", i, i), &lf_cebra, "Xavg", &format!("Cebra{}Energy", i), (600, 512), (fp_range, range)); - h.add_fill_hist2d(&format!("CeBrA/Cebra{}/Theta v Cebra{}RelTime ", i, i), &lf_cebra, &format!("Cebra{}RelTime", i), "Theta", (6400, 300), ((-3200.0, 3200.0), (0.0, PI))); - } - - // Apply timecut and shift RelTime if timecut is provided - if let Some(timecut) = timecut { - if sps { - // Shift RelTime by timecut.mean - let lf_timecut = lf_cebra.with_column( - (col(&format!("Cebra{}RelTime", i)) - lit(timecut.mean)).alias(&format!("Cebra{}RelTimeShifted", i)) - ) - .filter(col(&format!("Cebra{}RelTime", i)).gt(lit(timecut.low))) - .filter(col(&format!("Cebra{}RelTime", i)).lt(lit(timecut.high))) - .filter(col("AnodeBackTime").neq(lit(-1e6))); - - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/TimeCut/Cebra{}RelTime", i, i), &lf_timecut, &format!("Cebra{}RelTime", i), 6400, (-3200.0, 3200.0)); - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/TimeCut/Cebra{}RelTimeShifted", i, i), &lf_timecut, &format!("Cebra{}RelTimeShifted", i), 100, (-50.0, 50.0)); - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/TimeCut/Cebra{}Energy", i, i), &lf_timecut, &format!("Cebra{}Energy", i), 512, range); - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/TimeCut/Xavg", i), &lf_timecut, "Xavg", 600, fp_range); - - h.add_fill_hist1d("CeBrA/Xavg", &lf_timecut, "Xavg", 600, fp_range); - h.add_fill_hist1d("CeBrA/CebraRelTimeShifted_TimeCut", &lf_timecut, &format!("Cebra{}RelTimeShifted", i), 100, (-50.0, 50.0)); - - h.add_fill_hist2d(&format!("CeBrA/Cebra{}/TimeCut/Cebra{}Energy v Xavg", i, i), &lf_timecut, "Xavg", &format!("Cebra{}Energy", i), (600, 512), (fp_range, range)); - h.add_fill_hist2d(&format!("CeBrA/Cebra{}/TimeCut/Cebra{}Energy v X1", i, i), &lf_timecut, "X1", &format!("Cebra{}Energy", i), (600, 512), (fp_range, range)); - - if column_names.contains(&format!("Cebra{}EnergyGM", i)) { - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/TimeCut/GainMatched/Cebra{}EnergyGM", i, i), &lf_timecut, &format!("Cebra{}EnergyGM", i), 512, range); - - h.add_fill_hist2d(&format!("CeBrA/Cebra{}/TimeCut/GainMatched/Cebra{}EnergyGM v Xavg", i, i), &lf_timecut, "Xavg", &format!("Cebra{}EnergyGM", i), (600, 512), (fp_range, range)); - h.add_fill_hist2d("CeBrA/CebraEnergyGM v Xavg: TimeCut", &lf_timecut, "Xavg", &format!("Cebra{}EnergyGM", i), (600, 512), (fp_range, range)); - - h.add_fill_hist2d(&format!("CeBrA/Cebra{}/TimeCut/GainMatched/Cebra{}EnergyGM v X1", i, i), &lf_timecut, "X1", &format!("Cebra{}EnergyGM", i), (600, 512), (fp_range, range)); - h.add_fill_hist2d("CeBrA/CebraEnergyGM v X1: TimeCut", &lf_timecut, "X1", &format!("Cebra{}EnergyGM", i), (600, 512), (fp_range, range)); + let mut updated_configs = Configs::default(); + + let original_configs = self.sps_configs(); + + for config in &original_configs.configs { + match config { + Config::Hist1D(hist) => { + let mut cuts = hist.cuts.clone(); + + updated_configs.hist1d( + &hist.name.replacen("SE-SPS", "SE-SPS/No Cuts", 1), + &hist.column_name, + hist.range, + hist.bins, + Some(cuts.clone()), + ); + + cuts.merge(&active_cuts.clone()); + + updated_configs.hist1d( + &hist.name.replacen("SE-SPS", "SE-SPS/Cuts", 1), + &hist.column_name, + hist.range, + hist.bins, + Some(cuts.clone()), + ); + } + Config::Hist2D(hist) => { + let mut cuts = hist.cuts.clone(); + + updated_configs.hist2d( + &hist.name.replacen("SE-SPS", "SE-SPS/No Cuts", 1), + &hist.x_column_name, + &hist.y_column_name, + hist.x_range, + hist.y_range, + hist.bins, + Some(cuts.clone()), + ); + + cuts.merge(&active_cuts.clone()); + + updated_configs.hist2d( + &hist.name.replacen("SE-SPS", "SE-SPS/Cuts", 1), + &hist.x_column_name, + &hist.y_column_name, + hist.x_range, + hist.y_range, + hist.bins, + Some(cuts.clone()), + ); + } + } } - if let Some(ref ecal) = energy_calibration { - h.add_fill_hist1d(&format!("CeBrA/Cebra{}/TimeCut/Calibrated/Cebra{}EnergyCalibrated", i, i), &lf_timecut, &format!("Cebra{}EnergyCalibrated", i), ecal.bins, ecal.range); + updated_configs.columns = original_configs.columns.clone(); - h.add_fill_hist2d(&format!("CeBrA/Cebra{}/TimeCut/Calibrated/Cebra{}EnergyCalibrated v Xavg", i, i), &lf_timecut, "Xavg", &format!("Cebra{}EnergyCalibrated", i), (600, ecal.bins), (fp_range, ecal.range)); - h.add_fill_hist2d("CeBrA/CebraEnergyCalibrated v Xavg: TimeCut", &lf_timecut, "Xavg", &format!("Cebra{}EnergyCalibrated", i), (600, ecal.bins), (fp_range, ecal.range)); + updated_configs.cuts = original_configs.cuts.clone(); + updated_configs.cuts.merge(&active_cuts); - h.add_fill_hist2d(&format!("CeBrA/Cebra{}/TimeCut/Calibrated/Cebra{}EnergyCalibrated v X1", i, i), &lf_timecut, "X1", &format!("Cebra{}EnergyCalibrated", i), (600, ecal.bins), (fp_range, ecal.range)); - h.add_fill_hist2d("CeBrA/CebraEnergyCalibrated v X1: TimeCut", &lf_timecut, "X1", &format!("Cebra{}EnergyCalibrated", i), (600, ecal.bins), (fp_range, ecal.range)); - } + updated_configs } - }; - + } } +/* + #[rustfmt::skip] #[allow(clippy::all)] pub fn catrina(h: &mut Histogrammer, lf: LazyFrame, detector_number: usize) { diff --git a/src/ui/app.rs b/src/ui/app.rs index 9fa5598..b8081fb 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -33,12 +33,6 @@ impl eframe::App for Spectrix { } fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - // Handle keybinds - // let input = ctx.input(|state| state.clone()); // Get the input state - // if input.key_pressed(egui::Key::Tab) { - // self.processor.settings.dialog_open = !self.processor.settings.dialog_open; - // } - egui::TopBottomPanel::top("spectrix_top_panel").show(ctx, |ui| { egui::menu::bar(ui, |ui| { egui::global_theme_preference_switch(ui); diff --git a/src/util/processer.rs b/src/util/processer.rs index 0c0e8da..0be4204 100644 --- a/src/util/processer.rs +++ b/src/util/processer.rs @@ -41,7 +41,15 @@ pub struct Processor { impl Processor { pub fn new() -> Self { Self { - file_dialog: FileDialog::new(), + file_dialog: FileDialog::new() + .add_file_filter( + "Root files", + Arc::new(|p| p.extension().unwrap_or_default() == "root"), + ) + .add_file_filter( + "Parquet files", + Arc::new(|p| p.extension().unwrap_or_default() == "parquet"), + ), selected_files: Vec::new(), lazyframe: None, histogrammer: Histogrammer::default(), @@ -51,8 +59,7 @@ impl Processor { } pub fn reset(&mut self) { - self.lazyframe = None; - self.histogrammer = Histogrammer::default(); + *self = Self::new(); } pub fn get_histograms_from_root_files(&mut self) -> PyResult<()> { @@ -415,9 +422,12 @@ def get_2d_histograms(file_name): if ui.button("Clear").clicked() { self.selected_files.clear(); } - for file in self.selected_files.iter() { - ui.label(file.to_str().unwrap()); - } + // scrollable list of selected files + egui::ScrollArea::vertical().show(ui, |ui| { + for file in self.selected_files.iter() { + ui.label(file.to_str().unwrap()); + } + }); }, ); @@ -451,6 +461,21 @@ def get_2d_histograms(file_name): }); } + pub fn bottom_panel(&mut self, ctx: &egui::Context) { + if self.histogrammer.calculating.load(Ordering::Relaxed) { + egui::TopBottomPanel::bottom("spectrix_bottom_panel").show(ctx, |ui| { + ui.add( + egui::widgets::ProgressBar::new(match self.histogrammer.progress.lock() { + Ok(x) => *x, + Err(_) => 0.0, + }) + .animate(true) + .show_percentage(), + ); + }); + } + } + fn central_panel_ui(&mut self, ctx: &egui::Context) { egui::CentralPanel::default().show(ctx, |ui| { self.histogrammer.ui(ui); @@ -459,6 +484,7 @@ def get_2d_histograms(file_name): pub fn ui(&mut self, ctx: &egui::Context) { self.left_side_panels_ui(ctx); + self.bottom_panel(ctx); self.central_panel_ui(ctx); self.file_dialog.update(ctx);