diff --git a/Cargo.lock b/Cargo.lock index 89c2357..66019a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -846,6 +846,19 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -985,6 +998,27 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -1038,6 +1072,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.17" @@ -1142,6 +1182,19 @@ dependencies = [ "serde", ] +[[package]] +name = "egui-file-dialog" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac4167fcddb279f41863074b96b877c6e48c02d543b7d6111d1f3957b9a5874f" +dependencies = [ + "directories", + "dunce", + "egui 0.30.0", + "serde", + "sysinfo 0.33.0", +] + [[package]] name = "egui-wgpu" version = "0.30.0" @@ -1270,6 +1323,12 @@ dependencies = [ "serde", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "endi" version = "1.1.0" @@ -1353,9 +1412,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", @@ -2200,6 +2259,19 @@ dependencies = [ "serde", ] +[[package]] +name = "indicatif" +version = "0.17.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width 0.2.0", + "web-time", +] + [[package]] name = "indoc" version = "2.0.5" @@ -2721,6 +2793,12 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "objc" version = "0.2.7" @@ -2948,6 +3026,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.48" @@ -3635,7 +3719,7 @@ dependencies = [ "raw-cpuid", "rayon", "stacker", - "sysinfo", + "sysinfo 0.32.1", "version_check", ] @@ -3923,6 +4007,17 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "regex" version = "1.11.1" @@ -4099,18 +4194,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -4119,9 +4214,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -4303,6 +4398,7 @@ dependencies = [ "compute", "eframe", "egui 0.30.0", + "egui-file-dialog", "egui_extras", "egui_file", "egui_plot", @@ -4312,6 +4408,7 @@ dependencies = [ "find_peaks", "fnv", "geo", + "indicatif", "log", "nalgebra", "polars", @@ -4477,6 +4574,17 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "sysinfo" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "948512566b1895f93b1592c7574baeb2de842f224f2aab158799ecadb8ebbb46" +dependencies = [ + "core-foundation-sys", + "libc", + "windows 0.57.0", +] + [[package]] name = "target-features" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index f72d947..a494043 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,21 +18,22 @@ eframe = { version = "0.30", default-features = false, features = [ #"wayland", ] } egui = "0.30" +egui-file-dialog = "0.8.0" log = "0.4" -serde = { version = "1.0.216", features = ["derive"] } +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" epaint = "0.30" -env_logger = "0.11.5" +env_logger = "0.11.6" polars = { version = "0.45.0", features = ["lazy", "parquet", "performant"] } polars-lazy = { version = "0.45.0"} rayon = "1.10.0" rfd = "0.15.1" serde_yaml = "0.9.31" -serde_json = "1.0.133" +serde_json = "1.0.134" geo = "0.29.3" fnv = "1.0.7" varpro = "0.10.1" @@ -41,6 +42,7 @@ compute = "0.2.3" find_peaks = "0.1.5" pyo3 = { version = "0.22.0", features = ["auto-initialize"] } regex = "1.11.1" +indicatif = "0.17.9" [profile.release] opt-level = 2 # fast and small wasm diff --git a/src/egui_plot_stuff/egui_plot_settings.rs b/src/egui_plot_stuff/egui_plot_settings.rs index c8dff8e..c783610 100644 --- a/src/egui_plot_stuff/egui_plot_settings.rs +++ b/src/egui_plot_stuff/egui_plot_settings.rs @@ -145,7 +145,7 @@ impl EguiPlotSettings { let plot = if reset { self.reset_axis = false; - plot.auto_bounds(egui::Vec2b::new(true, true)) + plot.reset().auto_bounds(egui::Vec2b::new(true, true)) } else { plot }; diff --git a/src/histoer/configs.rs b/src/histoer/configs.rs index ca0dc34..554b536 100644 --- a/src/histoer/configs.rs +++ b/src/histoer/configs.rs @@ -1,6 +1,7 @@ -use super::cuts::Cut; +use super::cuts::{Cut, Cuts}; +use super::histogrammer::Histogrammer; -use egui_extras::{Column, TableBuilder, TableRow}; +use egui_extras::{Column, TableBuilder}; // Enum to encapsulate 1D and 2D histogram configurations #[derive(Clone, serde::Deserialize, serde::Serialize, Debug)] @@ -8,60 +9,356 @@ pub enum Config { Hist1D(Hist1DConfig), Hist2D(Hist2DConfig), } +#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, Default)] +pub struct Configs { + pub configs: Vec, + pub columns: Vec<(String, String)>, + pub cuts: Cuts, +} -impl Config { - /// Create a new 1D histogram configuration. - pub fn new_1d( +impl Configs { + pub fn hist1d( + &mut self, name: &str, column_name: &str, range: (f64, f64), bins: usize, - cuts: Option>, // Accept owned cuts directly - ) -> Self { + cuts: Option, + ) { let mut config = Hist1DConfig::new(name, column_name, range, bins); + if let Some(cuts) = cuts { config.cuts = cuts; } - Config::Hist1D(config) + + self.configs.push(Config::Hist1D(config)) } - /// Create a new 2D histogram configuration. - pub fn new_2d( + #[allow(clippy::too_many_arguments)] + pub fn hist2d( + &mut self, name: &str, x_column_name: &str, y_column_name: &str, x_range: (f64, f64), y_range: (f64, f64), bins: (usize, usize), - cuts: Option>, - ) -> Self { + cuts: Option, + ) { let mut config = Hist2DConfig::new(name, x_column_name, y_column_name, x_range, y_range, bins); + if let Some(cuts) = cuts { config.cuts = cuts; } - Config::Hist2D(config) + + self.configs.push(Config::Hist2D(config)); } - pub fn table_row(&mut self, row: &mut TableRow<'_, '_>, cuts: &mut Vec) { - match self { - Config::Hist1D(config) => config.table_row(row, cuts), - Config::Hist2D(config) => config.table_row(row, cuts), + pub fn merge(&mut self, other: Configs) -> &mut Self { + // Merge configurations + for config in other.configs { + match &config { + Config::Hist1D(other_hist1d) => { + if let Some(existing) = self + .configs + .iter() + .filter_map(|c| match c { + Config::Hist1D(h) if h.name == other_hist1d.name => Some(h), + _ => None, + }) + .next() + { + if existing.column_name != other_hist1d.column_name + && existing.range == other_hist1d.range + && existing.bins == other_hist1d.bins + { + self.configs.push(Config::Hist1D(other_hist1d.clone())); + } else { + log::error!( + "Conflict detected for Hist1D '{}' with column '{}' or range/bin mismatch.", + other_hist1d.name, + other_hist1d.column_name + ); + } + } else { + self.configs.push(Config::Hist1D(other_hist1d.clone())); + } + } + Config::Hist2D(other_hist2d) => { + if let Some(existing) = self + .configs + .iter() + .filter_map(|c| match c { + Config::Hist2D(h) if h.name == other_hist2d.name => Some(h), + _ => None, + }) + .next() + { + if (existing.x_column_name != other_hist2d.x_column_name + || existing.y_column_name != other_hist2d.y_column_name) + && existing.x_range == other_hist2d.x_range + && existing.y_range == other_hist2d.y_range + && existing.bins == other_hist2d.bins + { + self.configs.push(Config::Hist2D(other_hist2d.clone())); + } else { + log::error!( + "Conflict detected for Hist2D '{}' with columns ('{}', '{}') or range/bin mismatch.", + other_hist2d.name, + other_hist2d.x_column_name, + other_hist2d.y_column_name + ); + } + } else { + self.configs.push(Config::Hist2D(other_hist2d.clone())); + } + } + } + } + + // Merge columns + for (expression, alias) in other.columns { + if let Some(existing) = self.columns.iter().find(|(_, a)| a == &alias) { + if existing.0 != expression { + log::error!( + "Conflict detected for column alias '{}': Existing expression '{}', New expression '{}'.", + alias, + existing.0, + expression + ); + } + } else { + self.columns.push((expression, alias)); + } } + + // Merge cuts + self.cuts.merge(&other.cuts); + + self } -} -#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)] -pub struct Configs { - pub configs: Vec, -} + pub fn valid_configs(&mut self, lf: &mut LazyFrame) -> Configs { + // Add new computed columns to the LazyFrame + for (expression, alias) in &self.columns { + if let Err(e) = add_computed_column(lf, expression, alias) { + log::error!("Error adding computed column '{}': {}", alias, e); + } + } -impl Configs { - // pub fn validate_and_filter_columns(&mut self, lf_columns: Vec) -> Vec { + // Get the column names from the LazyFrame + let column_names = get_column_names_from_lazyframe(lf); + + // Ensure 1D cuts have their expressions parsed + self.cuts.parse_conditions(); + + // Expand the configurations (to account for patterns) + let expanded_configs = self.expand(); + + // Validate configurations and cuts + let mut valid_configs = Vec::new(); + let mut valid_cuts = Cuts::default(); + + for config in &expanded_configs.configs { + match config { + Config::Hist1D(hist1d) => { + if hist1d.calculate { + if column_names.contains(&hist1d.column_name) { + // Validate cuts for the histogram + let valid_hist_cuts: Vec = hist1d + .cuts + .cuts + .iter() + .filter(|cut| { + let required_columns = cut.required_columns(); + for column in required_columns { + if !column_names.contains(&column) { + log::error!( + "Invalid cut '{}' for 1D histogram '{}': Missing column '{}'", + cut.name(), + hist1d.name, + column + ); + return false; + } + } + true + }) + .cloned() + .collect(); + + let mut validated_hist1d = hist1d.clone(); + validated_hist1d.cuts = Cuts::new(valid_hist_cuts); + validated_hist1d.cuts.parse_conditions(); + valid_configs.push(Config::Hist1D(validated_hist1d)); + } else { + log::error!( + "Invalid 1D histogram '{}': Missing column '{}'", + hist1d.name, + hist1d.column_name + ); + } + } + } + Config::Hist2D(hist2d) => { + if hist2d.calculate { + if column_names.contains(&hist2d.x_column_name) + && column_names.contains(&hist2d.y_column_name) + { + // Validate cuts for the histogram + let valid_hist_cuts: Vec = hist2d + .cuts + .cuts + .iter() + .filter(|cut| { + let required_columns = cut.required_columns(); + for column in required_columns { + if !column_names.contains(&column) { + log::error!( + "Invalid cut '{}' for 2D histogram '{}': Missing column '{}'", + cut.name(), + hist2d.name, + column + ); + return false; + } + } + true + }) + .cloned() + .collect(); + + let mut validated_hist2d = hist2d.clone(); + validated_hist2d.cuts.cuts = valid_hist_cuts; + validated_hist2d.cuts.parse_conditions(); + valid_configs.push(Config::Hist2D(validated_hist2d)); + } else { + log::error!( + "Invalid 2D histogram '{}': Missing column(s) '{}', '{}'", + hist2d.name, + hist2d.x_column_name, + hist2d.y_column_name + ); + } + } + } + } + } + + // Validate cuts not associated with histograms + for cut in &self.cuts.cuts { + let required_columns = cut.required_columns(); + if required_columns + .iter() + .all(|col| column_names.contains(col)) + { + valid_cuts.add_cut(cut.clone()); + } else { + for column in required_columns { + if !column_names.contains(&column) { + log::error!( + "Invalid cut '{}': Missing required column '{}'", + cut.name(), + column + ); + } + } + } + } + + // Return a new Configs instance with validated configurations and cuts + Configs { + configs: valid_configs, + columns: self.columns.clone(), + cuts: valid_cuts, + } + } + + pub fn check_and_add_panes(&self, h: &mut Histogrammer) { + // reset all existings panes + h.reset_histograms(); + + // add panes that do not already exist in the histogrammer + for config in &self.configs { + match config { + Config::Hist1D(hist1d) => { + if let Some(_id) = h.find_existing_histogram(&hist1d.name) { + log::info!("Histogram {} already exists", hist1d.name); + } else { + h.add_hist1d(&hist1d.name, hist1d.bins, (hist1d.range.0, hist1d.range.1)); + } + } + Config::Hist2D(hist2d) => { + if let Some(_id) = h.find_existing_histogram(&hist2d.name) { + log::info!("Histogram {} already exists", hist2d.name); + } else { + h.add_hist2d( + &hist2d.name, + hist2d.bins, + ( + (hist2d.x_range.0, hist2d.x_range.1), + (hist2d.y_range.0, hist2d.y_range.1), + ), + ); + } + } + } + } + } + + pub fn get_used_columns(&self) -> Vec { + // Collect all column names currently used in the configurations and cuts + let mut used_column_names = Vec::new(); + for config in &self.configs { + match config { + Config::Hist1D(hist1d) => { + used_column_names.push(hist1d.column_name.clone()); + used_column_names.extend(hist1d.cuts.required_columns()); + } + Config::Hist2D(hist2d) => { + used_column_names.push(hist2d.x_column_name.clone()); + used_column_names.push(hist2d.y_column_name.clone()); + used_column_names.extend(hist2d.cuts.required_columns()); + } + } + } + + // Remove duplicates + used_column_names.sort(); + used_column_names.dedup(); + + used_column_names + } + + fn expand(&self) -> Configs { + let mut expanded_configs: Vec = Vec::new(); + + for config in &self.configs { + match config { + Config::Hist1D(config) => { + let expanded_1d = config.expand(); + for expanded_config in expanded_1d { + expanded_configs.push(Config::Hist1D(expanded_config)); + } + } + Config::Hist2D(config) => { + let expanded_2d = config.expand(); + for expanded_config in expanded_2d { + expanded_configs.push(Config::Hist2D(expanded_config)); + } + } + } + } - // } + Configs { + configs: expanded_configs, + columns: self.columns.clone(), + cuts: self.cuts.clone(), + } + } - pub fn ui(&mut self, ui: &mut egui::Ui, cuts: &mut Vec) { + pub fn config_ui(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { ui.heading("Histograms"); @@ -71,7 +368,7 @@ impl Configs { column_name: "".to_string(), range: (0.0, 4096.0), bins: 512, - cuts: vec![], + cuts: Cuts::default(), calculate: true, enabled: true, })); @@ -85,7 +382,7 @@ impl Configs { x_range: (0.0, 4096.0), y_range: (0.0, 4096.0), bins: (512, 512), - cuts: vec![], + cuts: Cuts::default(), calculate: true, enabled: true, })); @@ -145,7 +442,10 @@ impl Configs { } }); - config.table_row(&mut row, cuts); + match config { + Config::Hist1D(config) => config.table_row(&mut row, &mut self.cuts), + Config::Hist2D(config) => config.table_row(&mut row, &mut self.cuts), + } row.col(|ui| { if ui.button("X").clicked() { @@ -161,6 +461,136 @@ impl Configs { self.configs.remove(index); } } + + pub fn column_ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.heading("Column Creation"); + + if ui.button("+").clicked() { + self.columns.push(("".to_string(), "".to_string())); + } + + ui.separator(); + + if ui.button("Remove All").clicked() { + self.columns.clear(); + } + }); + + if !self.columns.is_empty() { + let mut indices_to_remove_column = Vec::new(); + + TableBuilder::new(ui) + .id_salt("new_columns") + .column(Column::auto()) // expression + .column(Column::auto()) // alias + .column(Column::remainder()) // Actions + .striped(true) + .vscroll(false) + .header(20.0, |mut header| { + header.col(|ui| { + ui.label("Alias"); + }); + header.col(|ui| { + ui.label("Expression"); + }); + }) + .body(|mut body| { + for (index, (expression, alias)) in self.columns.iter_mut().enumerate() { + body.row(18.0, |mut row| { + row.col(|ui| { + ui.add( + egui::TextEdit::singleline(alias) + .hint_text("Alias") + .clip_text(false), + ); + }); + + row.col(|ui| { + ui.add( + egui::TextEdit::singleline(expression) + .hint_text("Expression") + .clip_text(false), + ); + }); + + row.col(|ui| { + ui.horizontal(|ui| { + if ui.button("X").clicked() { + indices_to_remove_column.push(index); + } + }); + }); + }); + } + }); + + // Remove indices in reverse order to prevent shifting issues + for &index in indices_to_remove_column.iter().rev() { + self.columns.remove(index); + } + } + } + + pub fn cut_ui(&mut self, ui: &mut egui::Ui) { + self.cuts.ui(ui); + + // verify/sync cuts with histograms + for hist_config in &mut self.configs { + match hist_config { + Config::Hist1D(hist1d) => { + for hist_cut in &mut hist1d.cuts.cuts { + if let Some(updated_cut) = self + .cuts + .cuts + .iter() + .find(|cut| cut.name() == hist_cut.name()) + { + // Replace the cut if the operation or content has changed + *hist_cut = updated_cut.clone(); + } + } + + // Remove cuts that no longer exist in `self.cuts` + hist1d + .cuts + .cuts + .retain(|cut| self.cuts.cuts.iter().any(|c| c.name() == cut.name())); + } + Config::Hist2D(hist2d) => { + for hist_cut in &mut hist2d.cuts.cuts { + if let Some(updated_cut) = self + .cuts + .cuts + .iter() + .find(|cut| cut.name() == hist_cut.name()) + { + // Replace the cut if the operation or content has changed + *hist_cut = updated_cut.clone(); + } + } + + // Remove cuts that no longer exist in `self.cuts` + hist2d + .cuts + .cuts + .retain(|cut| self.cuts.cuts.iter().any(|c| c.name() == cut.name())); + } + } + } + } + + pub fn ui(&mut self, ui: &mut egui::Ui) { + self.column_ui(ui); + + ui.separator(); + + self.cut_ui(ui); + + ui.separator(); + + self.config_ui(ui); + } } #[derive(serde::Deserialize, serde::Serialize, Clone, Debug)] pub struct Hist1DConfig { @@ -168,7 +598,7 @@ pub struct Hist1DConfig { pub column_name: String, // Data column to fill from pub range: (f64, f64), // Range for the histogram pub bins: usize, // Number of bins - pub cuts: Vec, // Cuts for the histogram + pub cuts: Cuts, // Cuts for the histogram pub calculate: bool, // Whether to calculate the histogram pub enabled: bool, // Whether to let the user interact with the histogram } @@ -180,13 +610,13 @@ impl Hist1DConfig { column_name: column_name.to_string(), range, bins, - cuts: Vec::new(), + cuts: Cuts::default(), calculate: true, enabled: true, } } - pub fn table_row(&mut self, row: &mut egui_extras::TableRow<'_, '_>, cuts: &mut Vec) { + pub fn table_row(&mut self, row: &mut egui_extras::TableRow<'_, '_>, cuts: &mut Cuts) { row.col(|ui| { ui.add_enabled( self.enabled, @@ -233,25 +663,29 @@ impl Hist1DConfig { .selected_text("Select cuts") .width(ui.available_width()) .show_ui(ui, |ui| { - for cut in cuts { - let mut is_selected = - self.cuts.iter().any(|selected_cut| selected_cut == cut); + for cut in &cuts.cuts { + let mut is_selected = self + .cuts + .cuts + .iter() + .any(|selected_cut| selected_cut == cut); + match cut { Cut::Cut1D(cut1d) => { if ui.checkbox(&mut is_selected, &cut1d.name).clicked() { - if is_selected && !self.cuts.contains(cut) { - self.cuts.push(cut.clone()); + if is_selected && !self.cuts.cuts.contains(cut) { + self.cuts.cuts.push(cut.clone()); } else if !is_selected { - self.cuts.retain(|selected_cut| selected_cut != cut); + self.cuts.cuts.retain(|selected_cut| selected_cut != cut); } } } Cut::Cut2D(cut2d) => { if ui.checkbox(&mut is_selected, &cut2d.polygon.name).clicked() { - if is_selected && !self.cuts.contains(cut) { - self.cuts.push(cut.clone()); + if is_selected && !self.cuts.cuts.contains(cut) { + self.cuts.cuts.push(cut.clone()); } else if !is_selected { - self.cuts.retain(|selected_cut| selected_cut != cut); + self.cuts.cuts.retain(|selected_cut| selected_cut != cut); } } } @@ -264,6 +698,83 @@ impl Hist1DConfig { ui.add_enabled(self.enabled, egui::Checkbox::new(&mut self.calculate, "")); }); } + + pub fn expand(&self) -> Vec { + // Regex for range pattern `{start-end}` + let range_re = regex::Regex::new(r"\{(\d+)-(\d+)\}").unwrap(); + + // Regex for discrete comma-separated values `{val1,val2,...}` + let list_re = regex::Regex::new(r"\{([\d,]+)\}").unwrap(); + + let mut configs = Vec::new(); + + if self.calculate { + if self.name.contains("{}") { + // name has {} and column_name has a range pattern + if let Some(caps) = range_re.captures(&self.column_name) { + let start: usize = caps[1].parse().unwrap(); + let end: usize = caps[2].parse().unwrap(); + + // Loop through start and end values + for i in start..=end { + let mut new_config = self.clone(); + new_config.name = self.name.replace("{}", &i.to_string()).to_string(); + new_config.column_name = range_re + .replace(&self.column_name, i.to_string()) + .to_string(); + configs.push(new_config); + } + } + // name has {} and column_name has a list pattern + else if let Some(caps) = list_re.captures(&self.column_name) { + // Split comma-separated values and loop over them + let values: Vec<&str> = caps[1].split(',').collect(); + for val in values { + let mut new_config = self.clone(); + new_config.name = self.name.replace("{}", val).to_string(); + new_config.column_name = + list_re.replace(&self.column_name, val).to_string(); + configs.push(new_config); + } + // Unsupported pattern + } else { + log::error!( + "Warning: Unsupported pattern for 1D histogram with name '{}', column '{}'", + self.name, + self.column_name + ); + } + } else { + // No {} in name, but column_name has a range pattern + if let Some(caps) = range_re.captures(&self.column_name) { + let start: usize = caps[1].parse().unwrap(); + let end: usize = caps[2].parse().unwrap(); + + for i in start..=end { + let mut new_config = self.clone(); + new_config.column_name = range_re + .replace(&self.column_name, i.to_string()) + .to_string(); + configs.push(new_config); + } + } + // No {} in name, but column_name has a list pattern + else if let Some(caps) = list_re.captures(&self.column_name) { + let values: Vec<&str> = caps[1].split(',').collect(); + for val in values { + let mut new_config = self.clone(); + new_config.column_name = + list_re.replace(&self.column_name, val).to_string(); + configs.push(new_config); + } + // No {} in name or column_name i.e. a normal configuration + } else { + configs.push(self.clone()); + } + } + } + configs + } } #[derive(serde::Deserialize, serde::Serialize, Clone, Debug)] @@ -274,7 +785,7 @@ pub struct Hist2DConfig { pub x_range: (f64, f64), // Range for X-axis pub y_range: (f64, f64), // Range for Y-axis pub bins: (usize, usize), // Number of bins for X and Y axes - pub cuts: Vec, // Cuts for the histogram + pub cuts: Cuts, // Cuts for the histogram pub calculate: bool, // Whether to calculate the histogram pub enabled: bool, // Whether to let the user interact with the histogram } @@ -295,13 +806,13 @@ impl Hist2DConfig { x_range, y_range, bins, - cuts: Vec::new(), + cuts: Cuts::default(), calculate: true, enabled: true, } } - pub fn table_row(&mut self, row: &mut egui_extras::TableRow<'_, '_>, cuts: &mut Vec) { + pub fn table_row(&mut self, row: &mut egui_extras::TableRow<'_, '_>, cuts: &mut Cuts) { row.col(|ui| { ui.add_enabled( self.enabled, @@ -379,29 +890,33 @@ impl Hist2DConfig { }); row.col(|ui| { - egui::ComboBox::from_id_salt(format!("cut_select_1d_{}", self.name)) + egui::ComboBox::from_id_salt(format!("cut_select_2d_{}", self.name)) .selected_text("Select cuts") .width(ui.available_width()) .show_ui(ui, |ui| { - for cut in cuts { - let mut is_selected = - self.cuts.iter().any(|selected_cut| selected_cut == cut); + for cut in &cuts.cuts { + let mut is_selected = self + .cuts + .cuts + .iter() + .any(|selected_cut| selected_cut == cut); + match cut { Cut::Cut1D(cut1d) => { if ui.checkbox(&mut is_selected, &cut1d.name).clicked() { - if is_selected && !self.cuts.contains(cut) { - self.cuts.push(cut.clone()); + if is_selected && !self.cuts.cuts.contains(cut) { + self.cuts.cuts.push(cut.clone()); } else if !is_selected { - self.cuts.retain(|selected_cut| selected_cut != cut); + self.cuts.cuts.retain(|selected_cut| selected_cut != cut); } } } Cut::Cut2D(cut2d) => { if ui.checkbox(&mut is_selected, &cut2d.polygon.name).clicked() { - if is_selected && !self.cuts.contains(cut) { - self.cuts.push(cut.clone()); + if is_selected && !self.cuts.cuts.contains(cut) { + self.cuts.cuts.push(cut.clone()); } else if !is_selected { - self.cuts.retain(|selected_cut| selected_cut != cut); + self.cuts.cuts.retain(|selected_cut| selected_cut != cut); } } } @@ -414,4 +929,249 @@ impl Hist2DConfig { ui.add_enabled(self.enabled, egui::Checkbox::new(&mut self.calculate, "")); }); } + + pub fn expand(&self) -> Vec { + // Regex for range pattern `{start-end}` + let range_re = regex::Regex::new(r"\{(\d+)-(\d+)\}").unwrap(); + + // Regex for discrete comma-separated values `{val1,val2,...}` + let list_re = regex::Regex::new(r"\{([\d,]+)\}").unwrap(); + + let mut configs = Vec::new(); + + if self.calculate { + if self.name.contains("{}") { + // Case 1: `{}` in `name`, `x_column_name` has a pattern + if let Some(caps) = range_re.captures(&self.x_column_name) { + let start: usize = caps[1].parse().unwrap(); + let end: usize = caps[2].parse().unwrap(); + for i in start..=end { + let mut new_config = self.clone(); + new_config.name = self.name.replace("{}", &i.to_string()); + new_config.x_column_name = range_re + .replace(&self.x_column_name, i.to_string()) + .to_string(); + new_config.y_column_name = self.y_column_name.clone(); + configs.push(new_config); + } + } else if let Some(caps) = list_re.captures(&self.x_column_name) { + let values: Vec<&str> = caps[1].split(',').collect(); + for val in values { + let mut new_config = self.clone(); + new_config.name = self.name.replace("{}", val); + new_config.x_column_name = + list_re.replace(&self.x_column_name, val).to_string(); + new_config.y_column_name = self.y_column_name.clone(); + configs.push(new_config); + } + } + // Case 2: `{}` in `name`, `y_column_name` has a pattern + else if let Some(caps) = range_re.captures(&self.y_column_name) { + let start: usize = caps[1].parse().unwrap(); + let end: usize = caps[2].parse().unwrap(); + for i in start..=end { + let mut new_config = self.clone(); + new_config.name = self.name.replace("{}", &i.to_string()); + new_config.x_column_name = self.x_column_name.clone(); + new_config.y_column_name = range_re + .replace(&self.y_column_name, i.to_string()) + .to_string(); + configs.push(new_config); + } + } else if let Some(caps) = list_re.captures(&self.y_column_name) { + let values: Vec<&str> = caps[1].split(',').collect(); + for val in values { + let mut new_config = self.clone(); + new_config.name = self.name.replace("{}", val); + new_config.x_column_name = self.x_column_name.clone(); + new_config.y_column_name = + list_re.replace(&self.y_column_name, val).to_string(); + configs.push(new_config); + } + } else { + log::error!( + "Warning: Unsupported pattern for 2D histogram with name '{}', x_column '{}', y_column '{}'", + self.name, self.x_column_name, self.y_column_name + ); + } + } else { + // Static `name`, expand `x_column_name` or `y_column_name` with range or list patterns + if let Some(caps) = range_re.captures(&self.x_column_name) { + let start: usize = caps[1].parse().unwrap(); + let end: usize = caps[2].parse().unwrap(); + for i in start..=end { + let mut new_config = self.clone(); + new_config.x_column_name = range_re + .replace(&self.x_column_name, i.to_string()) + .to_string(); + configs.push(new_config); + } + } else if let Some(caps) = list_re.captures(&self.x_column_name) { + let values: Vec<&str> = caps[1].split(',').collect(); + for val in values { + let mut new_config = self.clone(); + new_config.x_column_name = + list_re.replace(&self.x_column_name, val).to_string(); + configs.push(new_config); + } + } else if let Some(caps) = range_re.captures(&self.y_column_name) { + let start: usize = caps[1].parse().unwrap(); + let end: usize = caps[2].parse().unwrap(); + for i in start..=end { + let mut new_config = self.clone(); + new_config.y_column_name = range_re + .replace(&self.y_column_name, i.to_string()) + .to_string(); + configs.push(new_config); + } + } else if let Some(caps) = list_re.captures(&self.y_column_name) { + let values: Vec<&str> = caps[1].split(',').collect(); + for val in values { + let mut new_config = self.clone(); + new_config.y_column_name = + list_re.replace(&self.y_column_name, val).to_string(); + configs.push(new_config); + } + } else { + configs.push(self.clone()); + } + } + } + configs + } +} + +use polars::prelude::*; +use regex::Regex; + +fn expr_from_string(expression: &str) -> Result { + let re = Regex::new(r"(-?\d+\.?\d*|\w+|\*\*|[+*/()-])").unwrap(); + let tokens: Vec = re + .find_iter(expression) + .map(|m| m.as_str().to_string()) + .collect(); + + 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 { + match token.as_str() { + "+" | "-" | "*" | "/" | "**" => { + 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)) + { + 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); + is_first_token = false; + } + ")" => { + while let Some(op) = op_stack.pop() { + if op == "(" { + break; + } + apply_op(&mut expr_stack, &op); + } + } + _ 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; + } + } + } + + while let Some(op) = op_stack.pop() { + apply_op(&mut expr_stack, &op); + } + + if expr_stack.len() == 1 { + Ok(expr_stack.pop().unwrap()) + } else { + log::error!("Error: Stack ended with more than one expression, invalid expression"); + Err(PolarsError::ComputeError("Invalid expression".into())) + } +} + +fn precedence(op: &str) -> i32 { + match op { + "+" | "-" => 1, + "*" | "/" => 2, + "**" => 3, + _ => 0, + } +} + +fn is_left_associative(op: &str) -> bool { + match op { + "+" | "-" | "*" | "/" => true, + "**" => false, // Exponentiation is right-associative + _ => false, + } +} + +fn apply_op(expr_stack: &mut Vec, operator: &str) { + if expr_stack.len() < 2 { + log::warn!("Error: Not enough operands for '{}'", operator); + return; + } + + let right = expr_stack.pop().unwrap(); + let left = expr_stack.pop().unwrap(); + + let result = match operator { + "+" => left + right, + "-" => left - right, + "*" => left * right, + "/" => left / right, + "**" => left.pow(right), + _ => { + log::error!("Unknown operator: '{}'", operator); + return; + } + }; + + expr_stack.push(result); +} + +fn add_computed_column( + lf: &mut LazyFrame, + expression: &str, + alias: &str, +) -> Result<(), PolarsError> { + let computed_expr = expr_from_string(expression)?; + *lf = lf.clone().with_column(computed_expr.alias(alias)); // Use alias for the new column name + Ok(()) +} + +pub fn get_column_names_from_lazyframe(lf: &LazyFrame) -> Vec { + let lf: LazyFrame = lf.clone().limit(1); + let df: DataFrame = lf.collect().unwrap(); + let columns: Vec = df + .get_column_names_owned() + .into_iter() + .map(|name| name.to_string()) + .collect(); + + columns } diff --git a/src/histoer/cuts.rs b/src/histoer/cuts.rs index aabd6c5..1985576 100644 --- a/src/histoer/cuts.rs +++ b/src/histoer/cuts.rs @@ -6,6 +6,7 @@ use std::io::{BufReader, Write}; use polars::prelude::*; use crate::egui_plot_stuff::egui_polygon::EguiPolygon; +use egui_extras::{Column, TableBuilder}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] pub enum Cut { @@ -42,16 +43,196 @@ impl Cut { } } + /// Returns the column(s) required by the cut + pub fn required_columns(&self) -> Vec { + match self { + Cut::Cut1D(cut1d) => cut1d.required_columns(), + Cut::Cut2D(cut2d) => cut2d.required_columns(), + } + } + pub fn new_1d(name: &str, expression: &str) -> Self { Cut::Cut1D(Cut1D::new(name, expression)) } } +#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, Default)] +pub struct Cuts { + pub cuts: Vec, +} + +impl Cuts { + pub fn new(cuts: Vec) -> Self { + Self { cuts } + } + + // Add a new cut + pub fn add_cut(&mut self, cut: Cut) { + if self.cuts.iter().any(|c| c.name() == cut.name()) { + log::error!("Cut with name '{}' already exists.", cut.name()); + } else { + self.cuts.push(cut); + } + } + + // Remove a cut by name + pub fn remove_cut(&mut self, name: &str) { + if let Some(pos) = self.cuts.iter().position(|cut| cut.name() == name) { + self.cuts.remove(pos); + } else { + log::error!("No cut with name '{}' found.", name); + } + } + + pub fn merge(&mut self, other: &Self) { + for cut in &other.cuts { + if !self.cuts.iter().any(|c| c.name() == cut.name()) { + self.cuts.push(cut.clone()); + } + } + } + + pub fn parse_conditions(&mut self) { + for cut in &mut self.cuts { + match cut { + Cut::Cut1D(cut1d) => cut1d.parse_conditions(), + Cut::Cut2D(_) => {} + } + } + } + + pub fn ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.heading("Cuts"); + + if ui.button("+1D").clicked() { + self.cuts.push(Cut::Cut1D(Cut1D::new("", ""))); + } + + if ui.button("+2D").clicked() { + // Create a new instance of Cut2D and attempt to load it from a JSON file + let mut new_cut2d = Cut2D::default(); + if new_cut2d.load_cut_from_json().is_ok() { + // If successfully loaded, add it to the cuts vector as a Cuts::Cut2D variant + self.cuts.push(Cut::Cut2D(new_cut2d)); + } else { + log::error!("Failed to load 2D cut from file."); + } + } + + ui.separator(); + + if ui.button("Remove All").clicked() { + self.cuts.clear(); + } + }); + + if !self.cuts.is_empty() { + let mut indices_to_remove_cut = Vec::new(); + + let mut cuts_1d = Vec::new(); + let mut cuts_2d = Vec::new(); + + self.cuts + .iter_mut() + .enumerate() + .for_each(|(i, cut)| match cut { + Cut::Cut1D(_) => cuts_1d.push((i, cut)), + Cut::Cut2D(_) => cuts_2d.push((i, cut)), + }); + + // Render 1D Cuts Table + if !cuts_1d.is_empty() { + ui.label("1D Cuts"); + TableBuilder::new(ui) + .id_salt("cuts_1d_table") + .column(Column::auto()) // Name + .column(Column::auto()) // Expression + .column(Column::auto()) // Active + .column(Column::remainder()) // Actions + .striped(true) + .vscroll(false) + .header(20.0, |mut header| { + header.col(|ui| { + ui.label("Name"); + }); + header.col(|ui| { + ui.label("Operation(s)"); + }); + }) + .body(|mut body| { + for (index, cut1d) in cuts_1d { + body.row(18.0, |mut row| { + cut1d.table_row(&mut row); + + row.col(|ui| { + if ui.button("X").clicked() { + indices_to_remove_cut.push(index); + } + }); + }); + } + }); + } + + if !cuts_2d.is_empty() { + ui.label("2D Cuts"); + TableBuilder::new(ui) + .id_salt("cuts_2d_table") + .column(Column::auto()) // Name + .column(Column::auto()) // X Column + .column(Column::auto()) // Y Column + .column(Column::remainder()) // Actions + .striped(true) + .vscroll(false) + .header(20.0, |mut header| { + header.col(|ui| { + ui.label("Name"); + }); + header.col(|ui| { + ui.label("X Column"); + }); + header.col(|ui| { + ui.label("Y Column"); + }); + }) + .body(|mut body| { + for (index, cut2d) in cuts_2d { + body.row(18.0, |mut row| { + cut2d.table_row(&mut row); + row.col(|ui| { + if ui.button("X").clicked() { + indices_to_remove_cut.push(index); + } + }); + }); + } + }); + } + + for &index in indices_to_remove_cut.iter().rev() { + self.cuts.remove(index); + } + } + } + + pub fn valid(&self, df: &DataFrame, row_idx: usize) -> bool { + self.cuts.iter().all(|cut| cut.valid(df, row_idx)) + } + + pub fn required_columns(&self) -> Vec { + self.cuts + .iter() + .flat_map(|cut| cut.required_columns()) + .collect() + } +} #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] pub struct Cut2D { pub polygon: EguiPolygon, pub x_column: String, pub y_column: String, + pub active: bool, } impl Default for Cut2D { @@ -60,6 +241,7 @@ impl Default for Cut2D { polygon: EguiPolygon::default(), x_column: "".to_string(), y_column: "".to_string(), + active: true, } } } @@ -102,6 +284,9 @@ impl Cut2D { .clip_text(false), ); }); + row.col(|ui| { + ui.add(egui::Checkbox::new(&mut self.active, "")); + }); } pub fn menu_button(&mut self, ui: &mut egui::Ui) { @@ -191,6 +376,10 @@ impl Cut2D { pub fn is_clicking(&self) -> bool { self.polygon.interactive_clicking } + + pub fn required_columns(&self) -> Vec { + vec![self.x_column.clone(), self.y_column.clone()] + } } // Struct to hold each parsed condition @@ -205,6 +394,7 @@ pub struct ParsedCondition { pub struct Cut1D { pub name: String, pub expression: String, // Logical expression to evaluate, e.g., "X1 != -1e6 & X2 == -1e6" + pub active: bool, #[serde(skip)] // Skip during serialization pub parsed_conditions: Option>, // Cache parsed conditions } @@ -214,6 +404,7 @@ impl Cut1D { Self { name: name.to_string(), expression: expression.to_string(), + active: true, parsed_conditions: None, } } @@ -233,6 +424,9 @@ impl Cut1D { .clip_text(false), ); }); + row.col(|ui| { + ui.add(egui::Checkbox::new(&mut self.active, "")); + }); } pub fn menu_button(&mut self, ui: &mut egui::Ui) { @@ -249,6 +443,17 @@ impl Cut1D { ); } + pub fn required_columns(&self) -> Vec { + self.parsed_conditions + .as_ref() + .map_or(vec![], |conditions| { + conditions + .iter() + .map(|cond| cond.column_name.clone()) + .collect() + }) + } + // Parse and cache conditions pub fn parse_conditions(&mut self) { self.parsed_conditions = None; // Reset parsed conditions @@ -339,6 +544,7 @@ impl Cut1D { } true // All conditions passed } else { + log::error!("No parsed conditions for Cut1D '{}'", self.name); false // Parsing failed or was not performed } } diff --git a/src/histoer/histo2d/colormaps.rs b/src/histoer/histo2d/colormaps.rs index 4de773d..3716b70 100644 --- a/src/histoer/histo2d/colormaps.rs +++ b/src/histoer/histo2d/colormaps.rs @@ -16,12 +16,12 @@ pub enum ColorMap { #[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct ColormapOptions { - log_norm: bool, - reverse: bool, - custom_display_range: bool, - remove: bool, - display_min: u64, - display_max: u64, + pub log_norm: bool, + pub reverse: bool, + pub custom_display_range: bool, + pub remove: bool, + pub display_min: u64, + pub display_max: u64, } impl Default for ColormapOptions { @@ -158,6 +158,22 @@ impl ColorMap { } } + pub fn next_colormap(&mut self) { + *self = match self { + ColorMap::Viridis => ColorMap::Fast, + ColorMap::Fast => ColorMap::SmoothCoolWarm, + ColorMap::SmoothCoolWarm => ColorMap::BentCoolWarm, + ColorMap::BentCoolWarm => ColorMap::Plasma, + ColorMap::Plasma => ColorMap::Blackbody, + ColorMap::Blackbody => ColorMap::Inferno, + ColorMap::Inferno => ColorMap::Kindlmann, + ColorMap::Kindlmann => ColorMap::ExtendedKindlmann, + ColorMap::ExtendedKindlmann => ColorMap::Turbo, + ColorMap::Turbo => ColorMap::Jet, + ColorMap::Jet => ColorMap::Viridis, + }; + } + fn colormap( color_data: Vec<(f32, i32, i32, i32)>, value: u64, diff --git a/src/histoer/histo2d/keybinds.rs b/src/histoer/histo2d/keybinds.rs index 139e3c1..7f6d148 100644 --- a/src/histoer/histo2d/keybinds.rs +++ b/src/histoer/histo2d/keybinds.rs @@ -21,6 +21,23 @@ impl Histogram2D { self.plot_settings.projections.add_y_projection = !self.plot_settings.projections.add_y_projection; } + + if ui.input(|i| i.key_pressed(egui::Key::Z)) { + self.plot_settings.colormap_options.log_norm = + !self.plot_settings.colormap_options.log_norm; + self.plot_settings.recalculate_image = true; + } + + if ui.input(|i| i.key_pressed(egui::Key::R)) { + self.plot_settings.colormap_options.reverse = + !self.plot_settings.colormap_options.reverse; + self.plot_settings.recalculate_image = true; + } + + if ui.input(|i| i.key_pressed(egui::Key::M)) { + self.plot_settings.colormap.next_colormap(); + self.plot_settings.recalculate_image = true; + } } } } diff --git a/src/histoer/histo2d/projections.rs b/src/histoer/histo2d/projections.rs index 62b772c..bd055c7 100644 --- a/src/histoer/histo2d/projections.rs +++ b/src/histoer/histo2d/projections.rs @@ -73,6 +73,15 @@ impl Histogram2D { .as_mut() .unwrap() .original_bins = bins; + + self.plot_settings + .projections + .y_projection + .as_mut() + .unwrap() + .plot_settings + .egui_settings + .reset_axis = true; } } else { // create a new histogram and set the bins @@ -132,6 +141,15 @@ impl Histogram2D { .as_mut() .unwrap() .original_bins = bins; + + self.plot_settings + .projections + .x_projection + .as_mut() + .unwrap() + .plot_settings + .egui_settings + .reset_axis = true; } } else { let mut x_histogram = Histogram::new( diff --git a/src/histoer/histogrammer.rs b/src/histoer/histogrammer.rs index e2bcbdb..15b7894 100644 --- a/src/histoer/histogrammer.rs +++ b/src/histoer/histogrammer.rs @@ -1,10 +1,10 @@ // External crates use egui_tiles::TileId; use fnv::FnvHashMap; +use indicatif::{ProgressBar, ProgressStyle}; use polars::prelude::*; use pyo3::{prelude::*, types::PyModule}; use rayon::prelude::*; -use regex::Regex; // Standard library use std::collections::HashMap; @@ -15,8 +15,7 @@ use std::sync::{ }; // Project modules -use super::configs::{Hist1DConfig, Hist2DConfig}; -use super::cuts::Cut; +use super::configs::{Config, Configs}; use super::histo1d::histogram1d::Histogram; use super::histo2d::histogram2d::Histogram2D; use super::pane::Pane; @@ -46,6 +45,8 @@ pub struct Histogrammer { pub behavior: TreeBehavior, #[serde(skip)] pub calculating: Arc, // Use AtomicBool for thread-safe status tracking + #[serde(skip)] + pub abort_flag: Arc, // Use AtomicBool for thread-safe abort flag pub histogram_map: HashMap, // Map full path to TabInfo } @@ -56,13 +57,14 @@ impl Default for Histogrammer { tree: egui_tiles::Tree::empty("Empty tree"), behavior: Default::default(), calculating: Arc::new(AtomicBool::new(false)), + abort_flag: Arc::new(AtomicBool::new(false)), histogram_map: HashMap::new(), } } } impl Histogrammer { - fn find_existing_histogram(&self, name: &str) -> Option { + pub fn find_existing_histogram(&self, name: &str) -> Option { self.tree.tiles.iter().find_map(|(id, tile)| { match tile { egui_tiles::Tile::Pane(Pane::Histogram(hist)) => { @@ -81,7 +83,7 @@ impl Histogrammer { }) } - fn _reset_histograms(&mut self) { + pub fn reset_histograms(&mut self) { for (_id, tile) in self.tree.tiles.iter_mut() { match tile { egui_tiles::Tile::Pane(Pane::Histogram(hist)) => { @@ -187,220 +189,92 @@ impl Histogrammer { pub fn fill_histograms( &mut self, - hist1d_specs: Vec, - hist2d_specs: Vec, + mut configs: Configs, lf: &LazyFrame, - new_columns: Vec<(String, String)>, - max_rows_per_batch: usize, + estimated_memory: f64, // chuck size in GB ) { - let mut lf = lf.clone(); - for (expression, alias) in new_columns { - if let Err(e) = Self::add_computed_column(&mut lf, &expression, &alias) { - log::error!("Error adding computed column '{}': {}", alias, e); - } - } - // Wrap `lf` in an `Arc` to safely share it between threads - let lf = Arc::new(lf.clone()); let calculating = Arc::clone(&self.calculating); + let abort_flag = Arc::clone(&self.abort_flag); // Set calculating to true at the start calculating.store(true, Ordering::SeqCst); + abort_flag.store(false, Ordering::SeqCst); - // Collect available column names - let available_columns = self.get_column_names_from_lazyframe(&lf); - - // Filter and validate histograms based on existing columns - let hist1d_specs: Vec<_> = hist1d_specs.into_iter().filter(|h| { - if h.calculate { - let column_exists = available_columns.contains(&h.column_name); - if !column_exists { - log::error!("Warning: Column '{}' does not exist for 1D histogram '{}'. Skipping.", h.column_name, h.name); - return false; - } - for cut in &h.cuts { - match cut { - Cut::Cut1D(cut1d) => { - if let Some(conditions) = &cut1d.parsed_conditions { - for condition in conditions { - if !available_columns.contains(&condition.column_name) { - log::error!( - "Warning: Cut column '{}' does not exist for 1D histogram '{}'. Skipping cut.", - condition.column_name, h.name - ); - return false; - } - } - } else { - log::error!("Warning: Failed to parse conditions for 1D cut '{}'. Skipping cut.", h.name); - return false; - } - } - Cut::Cut2D(cut2d) => { - if !available_columns.contains(&cut2d.x_column) { - log::error!("Warning: Cut column '{}' does not exist for 1D histogram '{}'. Skipping cut.", cut2d.x_column, h.name); - return false; - } - } - } - } - true - } else { - false - } - }).collect(); - - let hist2d_specs: Vec<_> = hist2d_specs.into_iter().filter(|h| { - if h.calculate { - let columns_exist = available_columns.contains(&h.x_column_name) && available_columns.contains(&h.y_column_name); - if !columns_exist { - log::error!("Warning: Columns '{}' or '{}' do not exist for 2D histogram '{}'. Skipping.", h.x_column_name, h.y_column_name, h.name); - return false; - } - for cut in &h.cuts { - match cut { - Cut::Cut1D(cut1d) => { - if let Some(conditions) = &cut1d.parsed_conditions { - for condition in conditions { - if !available_columns.contains(&condition.column_name) { - log::error!( - "Warning: Cut column '{}' does not exist for 1D histogram '{}'. Skipping cut.", - condition.column_name, h.name - ); - return false; - } - } - } else { - log::error!("Warning: Failed to parse conditions for 1D cut '{}'. Skipping cut.", h.name); - return false; - } - } - Cut::Cut2D(cut) => { - if !available_columns.contains(&cut.x_column) || !available_columns.contains(&cut.y_column) { - log::error!("Warning: Cut columns '{}' or '{}' do not exist for 2D histogram '{}'. Skipping cut.", cut.x_column, cut.y_column, h.name); - return false; - } - } - } - } - true - } else { - false - } - }).collect(); - - // Collect column names for the remaining histograms - let mut column_names: Vec<&str> = hist1d_specs - .iter() - .map(|h| h.column_name.as_str()) - .collect(); - - // Include columns required for cuts in both 1D and 2D histograms - for h in &hist1d_specs { - for cut in &h.cuts { - match cut { - Cut::Cut1D(cut) => { - for condition in cut.parsed_conditions.as_ref().unwrap() { - column_names.push(condition.column_name.as_str()); - } - } - Cut::Cut2D(cut) => { - column_names.push(cut.x_column.as_str()); - } - } - } - } - - for h in &hist2d_specs { - column_names.push(h.x_column_name.as_str()); - column_names.push(h.y_column_name.as_str()); - - for cut in &h.cuts { - match cut { - Cut::Cut1D(_) => {} - Cut::Cut2D(cut) => { - column_names.push(cut.x_column.as_str()); - column_names.push(cut.y_column.as_str()); - } - } - } - } + let mut lf = lf.clone(); - column_names.sort_unstable(); - column_names.dedup(); + let row_count = lf + .clone() + .select([len().alias("count")]) + .collect() + .unwrap() + .column("count") + .unwrap() + .u32() + .unwrap() + .get(0) + .unwrap(); + + // Validate configurations and prepare histograms + let valid_configs = configs.valid_configs(&mut lf); + valid_configs.check_and_add_panes(self); + + // Select required columns from the LazyFrame + let used_columns = valid_configs.get_used_columns(); + let selected_columns: Vec<_> = used_columns.iter().map(col).collect(); + + let columns = used_columns.len() as u64; + let rows = row_count as u64; + let estimated_gb = estimate_gb(rows, columns); + + // Estimate rows per chunk + let bytes_per_row = columns as f64 * 8.0; // Each f64 is 8 bytes + let chunk_size_bytes = estimated_memory * 1_073_741_824.0; + let rows_per_chunk = (chunk_size_bytes / bytes_per_row).floor() as usize; + + let progress_bar = ProgressBar::new(row_count as u64); + progress_bar.set_style( + ProgressStyle::default_bar() + .template( + "[{elapsed_precise}] {bar:40.cyan/blue} {percent}% ({pos}/{len}) ETA: {eta}", + ) + .expect("Failed to set progress bar template") + .progress_chars("#>-"), + ); + progress_bar.println(format!("Processing ~{:.2} GB of raw data", estimated_gb)); - // Map column names to expressions for LazyFrame selection - let selected_columns: Vec<_> = column_names.iter().map(|&col_name| col(col_name)).collect(); + // Apply the selection to the LazyFrame + let lf = Arc::new(lf.clone().select(selected_columns.clone())); - // Prepare collections for histograms + // Initialize histogram maps let mut hist1d_map = Vec::new(); let mut hist2d_map = Vec::new(); - let mut missing_1d = Vec::new(); - let mut missing_2d = Vec::new(); - - // Identify and reset existing histograms, collect missing ones - for h in &hist1d_specs { - if let Some((_id, egui_tiles::Tile::Pane(Pane::Histogram(hist)))) = - self.tree.tiles.iter_mut().find(|(_id, tile)| { - if let egui_tiles::Tile::Pane(Pane::Histogram(hist)) = tile { - hist.lock().unwrap().name == h.name - } else { - false - } - }) - { - hist.lock().unwrap().reset(); // Reset histogram counts - hist1d_map.push((Arc::clone(hist), h.clone())); - } else { - missing_1d.push(h.clone()); - } - } - - for h in &hist2d_specs { - if let Some((_id, egui_tiles::Tile::Pane(Pane::Histogram2D(hist)))) = - self.tree.tiles.iter_mut().find(|(_id, tile)| { - if let egui_tiles::Tile::Pane(Pane::Histogram2D(hist)) = tile { - hist.lock().unwrap().name == h.name - } else { - false - } - }) - { - hist.lock().unwrap().reset(); // Reset histogram counts - hist2d_map.push((Arc::clone(hist), h.clone())); - } else { - missing_2d.push(h.clone()); - } - } - // Add missing 1D histograms outside of the mutable borrow loop - for h in missing_1d { - self.add_hist1d(&h.name, h.bins, h.range); - if let Some((_id, egui_tiles::Tile::Pane(Pane::Histogram(hist)))) = - self.tree.tiles.iter_mut().find(|(_id, tile)| { - if let egui_tiles::Tile::Pane(Pane::Histogram(hist)) = tile { - hist.lock().unwrap().name == h.name - } else { - false + 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())); } - }) - { - hist1d_map.push((Arc::clone(hist), h)); - } - } - - // Add missing 2D histograms outside of the mutable borrow loop - for h in missing_2d { - self.add_hist2d(&h.name, h.bins, (h.x_range, h.y_range)); - if let Some((_id, egui_tiles::Tile::Pane(Pane::Histogram2D(hist)))) = - self.tree.tiles.iter_mut().find(|(_id, tile)| { - if let egui_tiles::Tile::Pane(Pane::Histogram2D(hist)) = tile { - hist.lock().unwrap().name == h.name - } else { - false + } + 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())); } - }) - { - hist2d_map.push((Arc::clone(hist), h)); + } } } @@ -408,193 +282,168 @@ impl Histogrammer { 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(); + move || { let mut row_start = 0; loop { - // Borrow the `LazyFrame` inside the Arc without moving it + if abort_flag.load(Ordering::SeqCst) { + println!("Processing aborted by user."); + break; + } + // Slice the LazyFrame into batches let batch_lf = lf .as_ref() .clone() - .slice(row_start as i64, max_rows_per_batch.try_into().unwrap()); - let lf_selected = batch_lf.select(selected_columns.clone()); + .slice(row_start as i64, rows_per_chunk.try_into().unwrap()); - // Break if no more rows are left to process - if lf_selected.clone().limit(1).collect().unwrap().height() == 0 { + // Break if no rows are left to process + if batch_lf.clone().limit(1).collect().unwrap().height() == 0 { break; } - // No need for an inner handle; use `par_iter` for parallel execution within this batch - if let Ok(df) = lf_selected.collect() { + if let Ok(df) = batch_lf.collect() { let height = df.height(); - // Parallel filling of 1D histograms - hist1d_map.par_iter().for_each(|(hist, meta)| { - if let Ok(col_idx) = df.column(&meta.column_name) { - if let Ok(col_values) = col_idx.f64() { - let mut hist = hist.lock().unwrap(); - - // Loop over each row, matching the row index in the DataFrame with the cut columns - for (index, value) in col_values.into_no_null_iter().enumerate() - { - if value == -1e6 { - continue; - } - - // Track if this point passes all cuts for this histogram - let mut passes_all_cuts = true; - - // Evaluate each cut for the current point - for cut in &meta.cuts { - // Call point_is_inside to check if this point satisfies the cut - if !cut.valid(&df, index) { - passes_all_cuts = false; - break; - } - } - - // Fill histogram only if all cuts pass for this point - if passes_all_cuts { - hist.fill(value); - } - - hist.plot_settings.progress = Some( - (row_start as f32 + index as f32) - / (df.height() as f32), - ); - } - } - } - }); - - hist2d_map.par_iter().for_each(|(hist, meta)| { - if let (Ok(x_values), Ok(y_values)) = ( - 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(); - - // Loop over each row, matching the row index in the DataFrame with the cut columns - for (index, (x, y)) in x_values - .into_no_null_iter() - .zip(y_values.into_no_null_iter()) - .enumerate() + // 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()) { - if x == -1e6 || y == -1e6 { - continue; - } - - // Track if this point passes all cuts for this histogram - let mut passes_all_cuts = true; - - // Evaluate each cut for the current point - for cut in &meta.cuts { - // Call point_is_inside to check if this point satisfies the cut - if !cut.valid(&df, index) { - passes_all_cuts = false; - break; - } - } - - // Fill histogram only if all cuts pass for this point - if passes_all_cuts { - hist.fill(x, y); - } + 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() } - } - }); - - // For histograms with vector columns - hist2d_map.par_iter().for_each(|(hist, meta)| { - if let (Ok(x_column), Ok(y_column)) = ( - df.column(&meta.x_column_name), - df.column(&meta.y_column_name), - ) { - if let (Ok(x_list), Ok(y_list)) = (x_column.list(), y_column.list()) - { - let mut hist = hist.lock().unwrap(); - - // Iterate over nested vector rows - for (index, (x_row, y_row)) in - x_list.into_iter().zip(y_list.into_iter()).enumerate() - { - 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(); - - for (x, y) in x_values - .into_no_null_iter() - .zip(y_values.into_no_null_iter()) + }) + .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) { - if x == -1e6 || y == -1e6 { - continue; - } - - // Evaluate cuts for the row - let mut passes_all_cuts = true; - for cut in &meta.cuts { - if !cut.valid(&df, index) { - passes_all_cuts = false; - break; - } - } - - if passes_all_cuts { - hist.fill(x, y); - } + 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() } - } - }); - - for (hist, meta) in &hist2d_map { - let mut hist = hist.lock().unwrap(); - 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; - } + }) + .collect(); + + // Fill 1D histograms + hist1d_map + .par_iter() + .zip(cached_bins_1d) + .for_each(|((hist, _), bins)| { + let mut hist = hist.lock().unwrap(); + bins.into_iter().for_each(|value| hist.fill(value)); + hist.plot_settings.egui_settings.reset_axis = true; + }); - for hist in &hist1d_map { - let mut hist = hist.0.lock().unwrap(); - hist.plot_settings.progress = None; - } + // 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)| { + let mut hist = hist.lock().unwrap(); + bins.into_iter().for_each(|(x, y)| hist.fill(x, y)); + }, + ); - println!("\tProcessed rows {} to {}", row_start, row_start + height); + progress_bar.inc(height as u64); } - row_start += max_rows_per_batch; + row_start += rows_per_chunk; } - println!("Finished processing all rows\n"); + progress_bar.finish_with_message("Processing complete."); // Set calculating to false when processing is complete calculating.store(false, Ordering::SeqCst); } }); } - fn add_computed_column( - lf: &mut LazyFrame, - expression: &str, - alias: &str, - ) -> Result<(), PolarsError> { - let computed_expr = expr_from_string(expression)?; - *lf = lf.clone().with_column(computed_expr.alias(alias)); // Use alias for the new column name - Ok(()) - } - - pub fn get_column_names_from_lazyframe(&self, lazyframe: &LazyFrame) -> Vec { - let lf: LazyFrame = lazyframe.clone().limit(1); - let df: DataFrame = lf.collect().unwrap(); - let columns: Vec = df - .get_column_names_owned() - .into_iter() - .map(|name| name.to_string()) - .collect(); - - columns - } - pub fn add_hist1d_with_bin_values( &mut self, name: &str, @@ -696,6 +545,10 @@ impl Histogrammer { if ui.button("Reorganize").clicked() { self.reorganize(); } + + if ui.button("Reset").clicked() { + *self = Default::default(); + } }); ui.separator(); @@ -1244,116 +1097,6 @@ def write_histograms(output_file, hist1d_data, hist2d_data): } } -fn expr_from_string(expression: &str) -> Result { - let re = Regex::new(r"(-?\d+\.?\d*|\w+|\*\*|[+*/()-])").unwrap(); - let tokens: Vec = re - .find_iter(expression) - .map(|m| m.as_str().to_string()) - .collect(); - - 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 { - match token.as_str() { - "+" | "-" | "*" | "/" | "**" => { - 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)) - { - 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); - is_first_token = false; - } - ")" => { - while let Some(op) = op_stack.pop() { - if op == "(" { - break; - } - apply_op(&mut expr_stack, &op); - } - } - _ 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; - } - } - } - - while let Some(op) = op_stack.pop() { - apply_op(&mut expr_stack, &op); - } - - if expr_stack.len() == 1 { - Ok(expr_stack.pop().unwrap()) - } else { - log::error!("Error: Stack ended with more than one expression, invalid expression"); - Err(PolarsError::ComputeError("Invalid expression".into())) - } -} - -fn precedence(op: &str) -> i32 { - match op { - "+" | "-" => 1, - "*" | "/" => 2, - "**" => 3, - _ => 0, - } -} - -fn is_left_associative(op: &str) -> bool { - match op { - "+" | "-" | "*" | "/" => true, - "**" => false, // Exponentiation is right-associative - _ => false, - } -} - -fn apply_op(expr_stack: &mut Vec, operator: &str) { - if expr_stack.len() < 2 { - log::warn!("Error: Not enough operands for '{}'", operator); - return; - } - - let right = expr_stack.pop().unwrap(); - let left = expr_stack.pop().unwrap(); - - let result = match operator { - "+" => left + right, - "-" => left - right, - "*" => left * right, - "/" => left / right, - "**" => left.pow(right), - _ => { - log::error!("Unknown operator: '{}'", operator); - return; - } - }; - - expr_stack.push(result); -} - fn tree_ui( ui: &mut egui::Ui, behavior: &mut dyn egui_tiles::Behavior, @@ -1408,3 +1151,10 @@ fn tree_ui( // Put the tile back tiles.insert(tile_id, tile); } + +fn estimate_gb(rows: u64, columns: u64) -> f64 { + // Each f64 takes 8 bytes + let total_bytes = rows * columns * 8; + // Convert bytes to gigabytes + total_bytes as f64 / 1024.0 / 1024.0 / 1024.0 +} diff --git a/src/histogram_scripter/custom_scripts.rs b/src/histogram_scripter/custom_scripts.rs new file mode 100644 index 0000000..06043ef --- /dev/null +++ b/src/histogram_scripter/custom_scripts.rs @@ -0,0 +1,627 @@ +use crate::histoer::{ + configs::Configs, + cuts::{Cut, Cuts}, +}; +use std::f64::consts::PI; + +#[derive(Clone, serde::Deserialize, serde::Serialize)] +pub struct CustomConfigs { + pub sps: SPSConfig, +} + +impl Default for CustomConfigs { + fn default() -> Self { + Self { + sps: SPSConfig::new(), + } + } +} + +impl CustomConfigs { + pub fn ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.label("Custom: "); + ui.checkbox(&mut self.sps.active, "SPS"); + }); + + if self.sps.active { + ui.horizontal(|ui| { + ui.collapsing("SE-SPS", |ui| { + if ui.button("Reset").clicked() { + self.sps = SPSConfig::new(); + self.sps.active = true; + } + self.sps.ui(ui); + }); + }); + } + } + + pub fn merge_active_configs(&self) -> Configs { + let mut configs = Configs::default(); + + if self.sps.active { + configs.merge(self.sps.configs.clone()); // Ensure `merge` handles in-place modifications + } + + configs + } +} + +#[derive(Clone, serde::Deserialize, serde::Serialize)] +pub struct Calibration { + pub name: String, + pub a: f64, + pub b: f64, + pub c: f64, + pub bins: usize, + pub range: (f64, f64), + pub active: bool, +} + +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: ")); + ui.add(egui::DragValue::new(&mut self.c).speed(0.01).prefix("c: ")); + + ui.separator(); + ui.add( + egui::DragValue::new(&mut self.bins) + .speed(1) + .prefix("Bins: "), + ); + ui.add( + egui::DragValue::new(&mut self.range.0) + .speed(1) + .prefix("Range: (") + .suffix(", "), + ); + ui.add( + egui::DragValue::new(&mut self.range.1) + .speed(1) + .suffix(") [keV]"), + ); + + ui.label(format!( + "keV/bin: {:.2}", + (self.range.1 - self.range.0) / self.bins as f64 + )); + } + ui.checkbox(&mut self.active, "Active"); + }); + } +} + +/*************************** 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 { + fn default() -> Self { + Self { + active: false, + xavg: Calibration { + name: "Xavg Energy Calibration:".into(), + a: 0.0, + b: 1.0, + c: 0.0, + bins: 512, + range: (0.0, 4096.0), + 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 + } + + pub fn ui(&mut self, ui: &mut egui::Ui) { + ui.separator(); + + ui.heading("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] + #[allow(clippy::all)] + pub fn sps_configs(&self) -> Configs { + let mut configs = Configs::default(); + + configs.columns.push(("( DelayFrontRightEnergy + DelayFrontLeftEnergy ) / 2.0".into(), "DelayFrontAverageEnergy".into())); + configs.columns.push(("( DelayBackRightEnergy + DelayBackLeftEnergy ) / 2.0".into(), "DelayBackAverageEnergy".into())); + configs.columns.push(("DelayFrontLeftTime - AnodeFrontTime".into(), "DelayFrontLeftTime_AnodeFrontTime".into())); + configs.columns.push(("DelayFrontRightTime - AnodeFrontTime".into(), "DelayFrontRightTime_AnodeFrontTime".into())); + configs.columns.push(("DelayBackLeftTime - AnodeFrontTime".into(), "DelayBackLeftTime_AnodeFrontTime".into())); + configs.columns.push(("DelayBackRightTime - AnodeFrontTime".into(), "DelayBackRightTime_AnodeFrontTime".into())); + configs.columns.push(("DelayFrontLeftTime - AnodeBackTime".into(), "DelayFrontLeftTime_AnodeBackTime".into())); + configs.columns.push(("DelayFrontRightTime - AnodeBackTime".into(), "DelayFrontRightTime_AnodeBackTime".into())); + configs.columns.push(("DelayBackLeftTime - AnodeBackTime".into(), "DelayBackLeftTime_AnodeBackTime".into())); + configs.columns.push(("DelayBackRightTime - AnodeBackTime".into(), "DelayBackRightTime_AnodeBackTime".into())); + configs.columns.push(("AnodeFrontTime - AnodeBackTime".into(), "AnodeFrontTime_AnodeBackTime".into())); + configs.columns.push(("AnodeBackTime - AnodeFrontTime".into(), "AnodeBackTime_AnodeFrontTime".into())); + configs.columns.push(("AnodeFrontTime - ScintLeftTime".into(), "AnodeFrontTime_ScintLeftTime".into())); + configs.columns.push(("AnodeBackTime - ScintLeftTime".into(), "AnodeBackTime_ScintLeftTime".into())); + configs.columns.push(("DelayFrontLeftTime - ScintLeftTime".into(), "DelayFrontLeftTime_ScintLeftTime".into())); + configs.columns.push(("DelayFrontRightTime - ScintLeftTime".into(), "DelayFrontRightTime_ScintLeftTime".into())); + configs.columns.push(("DelayBackLeftTime - ScintLeftTime".into(), "DelayBackLeftTime_ScintLeftTime".into())); + configs.columns.push(("DelayBackRightTime - ScintLeftTime".into(), "DelayBackRightTime_ScintLeftTime".into())); + 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(), + )); + } + + let mut cuts = Cuts::default(); + + let bothplanes_cut = Cut::new_1d("Both Planes", "X2 != -1e6 && X1 != -1e6"); + let only_x1_plane_cut = Cut::new_1d("Only X1 Plane", "X1 != -1e6 && X2 == -1e6"); + let only_x2_plane_cut = Cut::new_1d("Only X2 Plane", "X2 != -1e6 && X1 == -1e6"); + + cuts.add_cut(bothplanes_cut.clone()); + cuts.add_cut(only_x1_plane_cut.clone()); + cuts.add_cut(only_x2_plane_cut.clone()); + + let fp_range = (-300.0, 300.0); + let fp_bins = 600; + + let range = (0.0, 4096.0); + let bins = 512; + + // Focal plane histograms + configs.hist1d("SE-SPS/Focal Plane/X1", "X1", fp_range, fp_bins, None); + configs.hist1d("SE-SPS/Focal Plane/X2", "X2", fp_range, fp_bins, None); + configs.hist1d("SE-SPS/Focal Plane/Xavg", "Xavg", fp_range, fp_bins, None); + if self.xavg.active { + configs.hist1d("SE-SPS/Focal Plane/Xavg Energy Calibrated", "XavgEnergyCalibrated", self.xavg.range, self.xavg.bins, None); + } + 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); + + let cut_bothplanes = Some(Cuts::new(vec![bothplanes_cut.clone()])); + let cut_only_x1_plane = Some(Cuts::new(vec![only_x1_plane_cut])); + let cut_only_x2_plane = Some(Cuts::new(vec![only_x2_plane_cut])); + + configs.hist1d("SE-SPS/Focal Plane/Checks/Xavg", "Xavg", fp_range, fp_bins, None); + configs.hist1d("SE-SPS/Focal Plane/Checks/Raw- X1", "X1", fp_range, fp_bins, None); + configs.hist1d("SE-SPS/Focal Plane/Checks/Both Planes- X1", "X1", fp_range, fp_bins, cut_bothplanes.clone()); + configs.hist1d("SE-SPS/Focal Plane/Checks/Only 1 Plane- X1", "X1", fp_range, fp_bins, cut_only_x1_plane.clone()); + configs.hist1d("SE-SPS/Focal Plane/Checks/Raw- X2", "X2", fp_range, fp_bins, None); + configs.hist1d("SE-SPS/Focal Plane/Checks/Both Planes- X2", "X2", fp_range, fp_bins, cut_bothplanes.clone()); + configs.hist1d("SE-SPS/Focal Plane/Checks/Only 1 Plane- X2", "X2", fp_range, fp_bins, cut_only_x2_plane.clone()); + + // Particle Identification histograms + configs.hist2d("SE-SPS/Particle Identification/AnodeBack v ScintLeft", "ScintLeftEnergy", "AnodeBackEnergy", range, range, (bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification/AnodeFront v ScintLeft", "ScintLeftEnergy", "AnodeFrontEnergy", range, range, (bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification/Cathode v ScintLeft", "ScintLeftEnergy", "CathodeEnergy", range, range, (bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification/AnodeBack v ScintRight", "ScintRightEnergy", "AnodeBackEnergy", range, range, (bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification/AnodeFront v ScintRight", "ScintRightEnergy", "AnodeFrontEnergy", range, range, (bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification/Cathode v ScintRight", "ScintRightEnergy", "CathodeEnergy", range, range, (bins,bins), None); + + // Particle Identification vs Focal plane histograms + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/ScintLeft v X1", "X1", "ScintLeftEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/ScintLeft v X2", "X2", "ScintLeftEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/ScintLeft v Xavg", "Xavg", "ScintLeftEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/ScintRight v X1", "X1", "ScintRightEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/ScintRight v X2", "X2", "ScintRightEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/ScintRight v Xavg", "Xavg", "ScintRightEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/AnodeBack v X1", "X1", "AnodeBackEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/AnodeBack v X2", "X2", "AnodeBackEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/AnodeBack v Xavg", "Xavg", "AnodeBackEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/AnodeFront v X1", "X1", "AnodeFrontEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/AnodeFront v X2", "X2", "AnodeFrontEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/AnodeFront v Xavg", "Xavg", "AnodeFrontEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/Cathode v X1", "X1", "CathodeEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/Cathode v X2", "X2", "CathodeEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Particle Identification v Focal Plane/Cathode v Xavg", "Xavg", "CathodeEnergy", fp_range, range, (fp_bins,bins), None); + + // Delay lines vs Focal plane histograms + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontRight v X1", "X1", "DelayFrontRightEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontLeft v X1", "X1", "DelayFrontLeftEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayBackRight v X2", "X2", "DelayBackRightEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayBackLeft v X2", "X2", "DelayBackLeftEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayBackRight v Xavg", "Xavg", "DelayBackRightEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayBackLeft v Xavg", "Xavg", "DelayBackLeftEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontRight v Xavg", "Xavg", "DelayFrontRightEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontLeft v Xavg", "Xavg", "DelayFrontLeftEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayBackRight v X1", "X1", "DelayBackRightEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayBackLeft v X1", "X1", "DelayBackLeftEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontRight v X2", "X2", "DelayFrontRightEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontLeft v X2", "X2", "DelayFrontLeftEnergy", fp_range, range, (fp_bins,bins), None); + + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayFrontAverage v X1", "X1", "DelayFrontAverageEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayBackAverage v X1", "X1", "DelayBackAverageEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayFrontAverage v X2", "X2", "DelayFrontAverageEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayBackAverage v X2", "X2", "DelayBackAverageEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayFrontAverage v Xavg", "Xavg", "DelayFrontAverageEnergy", fp_range, range, (fp_bins,bins), None); + configs.hist2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayBackAverage v Xavg", "Xavg", "DelayBackAverageEnergy", fp_range, range, (fp_bins,bins), None); + + + // Delay timing relative to anodes histograms + let valid_sps_timing = Cut::new_1d("Valid SPS Timing", "AnodeBackTime != -1e6 && ScintLeftTime != -1e6"); + cuts.add_cut(valid_sps_timing.clone()); + + let cut_timing = Some(Cuts::new(vec![valid_sps_timing.clone()])); + + configs.hist1d("SE-SPS/Timing/AnodeFrontTime-AnodeBackTime", "AnodeFrontTime_AnodeBackTime", (-3000.0, 3000.0), 1000, cut_timing.clone()); + configs.hist1d("SE-SPS/Timing/AnodeBackTime-AnodeFrontTime", "AnodeBackTime_AnodeFrontTime", (-3000.0, 3000.0), 1000, cut_timing.clone()); + configs.hist1d("SE-SPS/Timing/AnodeFrontTime-ScintLeftTime", "AnodeFrontTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone()); + configs.hist1d("SE-SPS/Timing/AnodeBackTime-ScintLeftTime", "AnodeBackTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone()); + configs.hist1d("SE-SPS/Timing/DelayFrontLeftTime-ScintLeftTime", "DelayFrontLeftTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone()); + configs.hist1d("SE-SPS/Timing/DelayFrontRightTime-ScintLeftTime", "DelayFrontRightTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone()); + configs.hist1d("SE-SPS/Timing/DelayBackLeftTime-ScintLeftTime", "DelayBackLeftTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone()); + configs.hist1d("SE-SPS/Timing/DelayBackRightTime-ScintLeftTime", "DelayBackRightTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone()); + configs.hist1d("SE-SPS/Timing/ScintRightTime-ScintLeftTime", "ScintRightTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone()); + configs.hist2d("SE-SPS/Timing/ScintTimeDif v Xavg", "Xavg", "ScintRightTime_ScintLeftTime", fp_range, (-3200.0, 3200.0), (fp_bins, 12800), cut_timing.clone()); + + + configs.hist1d("SE-SPS/Timing/Both Planes/DelayFrontLeftTime-AnodeFrontTime", "DelayFrontLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_bothplanes.clone()); + configs.hist1d("SE-SPS/Timing/Both Planes/DelayFrontRightTime-AnodeFrontTime", "DelayFrontRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_bothplanes.clone()); + configs.hist1d("SE-SPS/Timing/Both Planes/DelayBackLeftTime-AnodeBackTime", "DelayBackLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_bothplanes.clone()); + configs.hist1d("SE-SPS/Timing/Both Planes/DelayBackRightTime-AnodeBackTime", "DelayBackRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_bothplanes.clone()); + + configs.hist1d("SE-SPS/Timing/Only X1 Plane/DelayFrontLeftTime-AnodeFrontTime", "DelayFrontLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X1 Plane/DelayFrontRightTime-AnodeFrontTime", "DelayFrontRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X1 Plane/DelayBackLeftTime-AnodeFrontTime", "DelayBackLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X1 Plane/DelayBackRightTime-AnodeFrontTime", "DelayBackRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X1 Plane/DelayFrontLeftTime-AnodeBackTime", "DelayFrontLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X1 Plane/DelayFrontRightTime-AnodeBackTime", "DelayFrontRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X1 Plane/DelayBackLeftTime-AnodeBackTime", "DelayBackLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X1 Plane/DelayBackRightTime-AnodeBackTime", "DelayBackRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone()); + + configs.hist1d("SE-SPS/Timing/Only X2 Plane/DelayFrontLeftTime-AnodeFrontTime", "DelayFrontLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X2 Plane/DelayFrontRightTime-AnodeFrontTime", "DelayFrontRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X2 Plane/DelayBackLeftTime-AnodeFrontTime", "DelayBackLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X2 Plane/DelayBackRightTime-AnodeFrontTime", "DelayBackRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X2 Plane/DelayFrontLeftTime-AnodeBackTime", "DelayFrontLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X2 Plane/DelayFrontRightTime-AnodeBackTime", "DelayFrontRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X2 Plane/DelayBackLeftTime-AnodeBackTime", "DelayBackLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone()); + configs.hist1d("SE-SPS/Timing/Only X2 Plane/DelayBackRightTime-AnodeBackTime", "DelayBackRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone()); + + configs.cuts = cuts; + + configs + } + + // fn update_configs_with_cuts(&self) -> Configs { + // } +} + +/*************************** CeBrA Cutsom Struct ***************************/ + +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)) + ) + } 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)); + } + + 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); + + 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)); + + 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)); + } + } + }; + +} + +#[rustfmt::skip] +#[allow(clippy::all)] +pub fn catrina(h: &mut Histogrammer, lf: LazyFrame, detector_number: usize) { + let i = detector_number; + + h.add_fill_hist1d(&format!("Catrina/CATRINA{i}/Energy"), &lf, &format!("CATRINA{i}Energy"), 4096, range); + h.add_fill_hist2d(&format!("Catrina/CATRINA{i}/PSD vs Energy"), &lf, &format!("CATRINA{i}Energy"), &format!("CATRINA{i}PSD"), (512, 500), (range, (0.0, 1.0))); +} + + +*/ + +// pips1000(h, lf); +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo.... + +// sps_histograms(h, lf.clone()); + +// For 52Cr(d,pg)53Cr +// let det_0_timecut = TimeCut { mean: -1155.6, low: -1158.0, high: -1152.0}; +// let det_1_timecut = TimeCut { mean: -1153.9, low: -1159.0, high: -1147.0}; +// let det_2_timecut = TimeCut { mean: -1154.0, low: -1158.0, high: -1151.0}; +// let det_3_timecut = TimeCut { mean: -1152.0, low: -1158.0, high: -1148.0}; +// let det_4_timecut = TimeCut { mean: -1123.1, low: -1127.0, high: -1118.0}; + +// // These values were gain match to detector 0 +// // let det_0_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; +// // let det_1_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; +// // let det_2_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; +// // let det_3_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; +// // let det_4_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; + +// let det_0_energy_calibration = EnergyCalibration { a: 0.0, b: 1.7551059351549314, c: -12.273506897222896, bins: 1024, range: (0.0, 16384.0) }; +// let det_1_energy_calibration = EnergyCalibration { a: 0.0, b: 1.9510278378962256, c: -16.0245754973971, bins: 1024, range: (0.0, 16384.0) }; +// let det_2_energy_calibration = EnergyCalibration { a: 0.0, b: 1.917190081718234, c: 16.430212777833802, bins: 1024, range: (0.0, 16384.0) }; +// let det_3_energy_calibration = EnergyCalibration { a: 0.0, b: 1.6931918955746692, c: 12.021258506937766, bins: 1024, range: (0.0, 16384.0) }; +// let det_4_energy_calibration = EnergyCalibration { a: 0.0, b: 1.6373533248536343, c: 13.091030061910748, bins: 1024, range: (0.0, 16384.0) }; + +// cebra(h, lf.clone(), 0, Some(det_0_timecut), None, Some(det_0_energy_calibration)); +// cebra(h, lf.clone(), 1, Some(det_1_timecut), None, Some(det_1_energy_calibration)); +// cebra(h, lf.clone(), 2, Some(det_2_timecut), None, Some(det_2_energy_calibration)); +// cebra(h, lf.clone(), 3, Some(det_3_timecut), None, Some(det_3_energy_calibration)); +// cebra(h, lf.clone(), 4, Some(det_4_timecut), None, Some(det_4_energy_calibration)); + +// #[rustfmt::skip] +// #[allow(clippy::all)] +// pub fn pips1000(h: &mut Histogrammer, lf: LazyFrame) { +// let lf_pips = lf.with_columns( vec![ +// // ( ( col("PIPS1000Energy") - col("PIPS1000Short") )/ col("PIPS1000Energy") ).alias("PIPS1000PSD"), +// (lit(-1.77049e-06)*col("PIPS1000Energy")*col("PIPS1000Energy") + lit(0.544755003513083)*col("PIPS1000Energy") + lit(-1.36822594543883)).alias("PIPS1000EnergyCalibrated") ] +// ); + +// h.add_fill_hist1d("PIPS1000/Energy", &lf_pips, "PIPS1000Energy", 16384, (0.0, 16384.0)); +// // h.add_fill_hist2d("PIPS1000: PSD", &lf_pips, "PIPS1000Energy", "PIPS1000PSD", (512, 500), (range, (0.0, 1.0))); +// h.add_fill_hist1d("PIPS1000/Energy Calibrated", &lf_pips, "PIPS1000EnergyCalibrated", 600, (0.0, 1200.0)); + +// } diff --git a/src/histogram_scripter/histogram_script.rs b/src/histogram_scripter/histogram_script.rs index 0a47815..3c0b0ae 100644 --- a/src/histogram_scripter/histogram_script.rs +++ b/src/histogram_scripter/histogram_script.rs @@ -1,746 +1,47 @@ -// use super::configure_auxillary_detectors::AuxillaryDetectors; -use super::manual_histogram_scripts::sps_histograms; +use super::custom_scripts::CustomConfigs; -use crate::histoer::configs::{Config, Hist1DConfig, Hist2DConfig}; -use crate::histoer::cuts::{Cut, Cut1D, Cut2D}; +use crate::histoer::configs::Configs; use crate::histoer::histogrammer::Histogrammer; -use egui_extras::{Column, TableBuilder}; use polars::prelude::*; -use rfd::FileDialog; -use serde_json; -use std::fs::File; -use std::io::{self, BufReader, BufWriter}; - #[derive(Clone, Default, serde::Deserialize, serde::Serialize)] pub struct HistogramScript { - pub hist_configs: Vec, // Unified vector for both 1D and 2D configurations - pub new_columns: Vec<(String, String)>, - pub cuts: Vec, + pub configs: Configs, + pub custom_scripts: CustomConfigs, } impl HistogramScript { pub fn new() -> Self { Self { - hist_configs: vec![], - new_columns: vec![], - cuts: vec![], - } - } - - fn histogram_exists(&self, name: &str) -> bool { - self.hist_configs.iter().any(|config| match config { - Config::Hist1D(hist) => hist.name == name, - Config::Hist2D(hist) => hist.name == name, - }) - } - - fn column_exists(&self, alias: &str) -> bool { - self.new_columns - .iter() - .any(|(_, col_alias)| col_alias == alias) - } - - fn column_creation_ui(&mut self, ui: &mut egui::Ui) { - ui.horizontal(|ui| { - ui.heading("Column Creation"); - - if ui.button("+").clicked() { - self.new_columns.push(("".to_string(), "".to_string())); - } - - ui.separator(); - - if ui.button("Remove All").clicked() { - self.new_columns.clear(); - } - }); - - if !self.new_columns.is_empty() { - let mut indices_to_remove_column = Vec::new(); - - TableBuilder::new(ui) - .id_salt("new_columns") - .column(Column::auto()) // expression - .column(Column::auto()) // alias - .column(Column::remainder()) // Actions - .striped(true) - .vscroll(false) - .header(20.0, |mut header| { - header.col(|ui| { - ui.label("Alias"); - }); - header.col(|ui| { - ui.label("Expression"); - }); - }) - .body(|mut body| { - for (index, (expression, alias)) in self.new_columns.iter_mut().enumerate() { - body.row(18.0, |mut row| { - row.col(|ui| { - ui.add( - egui::TextEdit::singleline(alias) - .hint_text("Alias") - .clip_text(false), - ); - }); - - row.col(|ui| { - ui.add( - egui::TextEdit::singleline(expression) - .hint_text("Expression") - .clip_text(false), - ); - }); - - row.col(|ui| { - ui.horizontal(|ui| { - if ui.button("X").clicked() { - indices_to_remove_column.push(index); - } - }); - }); - }); - } - }); - - // Remove indices in reverse order to prevent shifting issues - for &index in indices_to_remove_column.iter().rev() { - self.new_columns.remove(index); - } - } - } - - fn histogram_script_ui(&mut self, ui: &mut egui::Ui) { - ui.label("Custom Histogram Scripts"); - ui.horizontal(|ui| { - if ui.button("SE-SPS").clicked() { - let (columns, histograms, cuts) = sps_histograms(); - for histogram in histograms { - match &histogram { - Config::Hist1D(histo1d) => { - if !self.histogram_exists(&histo1d.name) { - self.hist_configs.push(Config::Hist1D(histo1d.clone())); - } - } - Config::Hist2D(histo2d) => { - if !self.histogram_exists(&histo2d.name) { - self.hist_configs.push(Config::Hist2D(histo2d.clone())); - } - } - } - } - - // Only add columns if the alias is unique - for (expression, alias) in columns { - if !self.column_exists(&alias) { - self.new_columns.push((expression, alias)); - } - } - - self.cuts = cuts; - } - - ui.separator(); - - if ui.button("Save Script").clicked() { - if let Err(e) = self.save_histogram_script() { - log::error!("Failed to save script: {}", e); - } - } - if ui.button("Load Script").clicked() { - if let Err(e) = self.load_histogram_script() { - log::error!("Failed to load script: {}", e); - } - } - }); - } - - fn cut_ui(&mut self, ui: &mut egui::Ui) { - ui.horizontal(|ui| { - ui.heading("Cuts"); - - if ui.button("+1D").clicked() { - // Add logic to create a new 1D cut and add it to `self.cuts` - // For example: - self.cuts.push(Cut::Cut1D(Cut1D::new("", ""))); - } - - if ui.button("+2D").clicked() { - // Create a new instance of Cut2D and attempt to load it from a JSON file - let mut new_cut2d = Cut2D::default(); - if new_cut2d.load_cut_from_json().is_ok() { - // If successfully loaded, add it to the cuts vector as a Cuts::Cut2D variant - self.cuts.push(Cut::Cut2D(new_cut2d)); - } else { - log::error!("Failed to load 2D cut from file."); - } - } - - ui.separator(); - - if ui.button("Remove All").clicked() { - self.cuts.clear(); - for hist_config in &mut self.hist_configs { - match hist_config { - Config::Hist1D(hist1d) => { - hist1d.cuts.clear(); - } - Config::Hist2D(hist2d) => { - hist2d.cuts.clear(); - } - } - } - } - }); - - if !self.cuts.is_empty() { - let mut indices_to_remove_cut = Vec::new(); - - let mut cuts_1d = Vec::new(); - let mut cuts_2d = Vec::new(); - - self.cuts - .iter_mut() - .enumerate() - .for_each(|(i, cut)| match cut { - Cut::Cut1D(_) => cuts_1d.push((i, cut)), - Cut::Cut2D(_) => cuts_2d.push((i, cut)), - }); - - // Render 1D Cuts Table - if !cuts_1d.is_empty() { - ui.label("1D Cuts"); - TableBuilder::new(ui) - .id_salt("cuts_1d_table") - .column(Column::auto()) // Name - .column(Column::auto()) // Expression - .column(Column::remainder()) // Actions - .striped(true) - .vscroll(false) - .header(20.0, |mut header| { - header.col(|ui| { - ui.label("Name"); - }); - header.col(|ui| { - ui.label("Operation(s)"); - }); - }) - .body(|mut body| { - for (index, cut1d) in cuts_1d { - body.row(18.0, |mut row| { - cut1d.table_row(&mut row); - - row.col(|ui| { - ui.horizontal(|ui| { - if ui.button("Apply to All").clicked() { - for hist_config in &mut self.hist_configs { - match hist_config { - Config::Hist1D(hist1d) => { - if !hist1d.cuts.contains(cut1d) { - hist1d.cuts.push(cut1d.clone()); - } - } - Config::Hist2D(hist2d) => { - if !hist2d.cuts.contains(cut1d) { - hist2d.cuts.push(cut1d.clone()); - } - } - } - } - } - - if ui.button("Remove from All").clicked() { - for hist_config in &mut self.hist_configs { - match hist_config { - Config::Hist1D(hist1d) => { - hist1d.cuts.retain(|cut| cut != cut1d); - } - Config::Hist2D(hist2d) => { - hist2d.cuts.retain(|cut| cut != cut1d); - } - } - } - } - - if ui.button("X").clicked() { - indices_to_remove_cut.push(index); - } - }); - }); - }); - } - }); - } - - if !cuts_2d.is_empty() { - ui.label("2D Cuts"); - TableBuilder::new(ui) - .id_salt("cuts_2d_table") - .column(Column::auto()) // Name - .column(Column::auto()) // X Column - .column(Column::auto()) // Y Column - .column(Column::remainder()) // Actions - .striped(true) - .vscroll(false) - .header(20.0, |mut header| { - header.col(|ui| { - ui.label("Name"); - }); - header.col(|ui| { - ui.label("X Column"); - }); - header.col(|ui| { - ui.label("Y Column"); - }); - }) - .body(|mut body| { - for (index, cut2d) in cuts_2d { - body.row(18.0, |mut row| { - cut2d.table_row(&mut row); - row.col(|ui| { - ui.horizontal(|ui| { - if ui.button("Apply to All").clicked() { - for hist_config in &mut self.hist_configs { - match hist_config { - Config::Hist1D(hist1d) => { - if !hist1d.cuts.contains(cut2d) { - hist1d.cuts.push(cut2d.clone()); - } - } - Config::Hist2D(hist2d) => { - if !hist2d.cuts.contains(cut2d) { - hist2d.cuts.push(cut2d.clone()); - } - } - } - } - } - - if ui.button("Remove from All").clicked() { - for hist_config in &mut self.hist_configs { - match hist_config { - Config::Hist1D(hist1d) => { - hist1d.cuts.retain(|cut| cut != cut2d); - } - Config::Hist2D(hist2d) => { - hist2d.cuts.retain(|cut| cut != cut2d); - } - } - } - } - - if ui.button("X").clicked() { - indices_to_remove_cut.push(index); - } - }); - }); - }); - } - }); - } - - for &index in indices_to_remove_cut.iter().rev() { - self.cuts.remove(index); - } - } - } - - fn histogram_ui(&mut self, ui: &mut egui::Ui) { - ui.horizontal(|ui| { - ui.heading("Histograms"); - - if ui.button("+1D").clicked() { - self.hist_configs.push(Config::Hist1D(Hist1DConfig { - name: "".to_string(), - column_name: "".to_string(), - range: (0.0, 4096.0), - bins: 512, - cuts: vec![], - calculate: true, - enabled: true, - })); - } - - if ui.button("+2D").clicked() { - self.hist_configs.push(Config::Hist2D(Hist2DConfig { - name: "".to_string(), - x_column_name: "".to_string(), - y_column_name: "".to_string(), - x_range: (0.0, 4096.0), - y_range: (0.0, 4096.0), - bins: (512, 512), - cuts: vec![], - calculate: true, - enabled: true, - })); - } - - ui.separator(); - - if ui.button("Remove All").clicked() { - self.hist_configs.clear(); - } - }); - - let mut indices_to_remove = Vec::new(); - - // Create the table - TableBuilder::new(ui) - .id_salt("hist_configs") - .column(Column::auto()) // Type - .column(Column::auto()) // Name - .column(Column::auto()) // Columns - .column(Column::auto()) // Ranges - .column(Column::auto()) // Bins - .column(Column::auto()) // cuts - .column(Column::auto()) // Actions - .column(Column::remainder()) // remove - .striped(true) - .vscroll(false) - .header(20.0, |mut header| { - header.col(|ui| { - ui.label(" # "); - }); - header.col(|ui| { - ui.label("Name"); - }); - header.col(|ui| { - ui.label("Column(s)"); - }); - header.col(|ui| { - ui.label("Range(s)"); - }); - header.col(|ui| { - ui.label("Bins"); - }); - header.col(|ui| { - ui.label("Cuts"); - }); - }) - .body(|mut body| { - for (index, config) in self.hist_configs.iter_mut().enumerate() { - body.row(18.0, |mut row| { - row.col(|ui| match config { - Config::Hist1D(_) => { - ui.label(format!("{index}")); - } - Config::Hist2D(_) => { - ui.label(format!("{index}")); - } - }); - - config.table_row(&mut row, &mut self.cuts); - - row.col(|ui| { - if ui.button("X").clicked() { - indices_to_remove.push(index); - } - }); - }); - } - }); - - // Remove indices in reverse order to prevent shifting issues - for &index in indices_to_remove.iter().rev() { - self.hist_configs.remove(index); - } - } - - fn verify_cuts(&mut self) { - // Synchronize cuts after all UI interactions - for hist_config in &mut self.hist_configs { - match hist_config { - Config::Hist1D(hist1d) => { - for hist_cut in &mut hist1d.cuts { - if let Some(updated_cut) = - self.cuts.iter().find(|cut| cut.name() == hist_cut.name()) - { - // Replace the cut if the operation or content has changed - *hist_cut = updated_cut.clone(); - } - } - - // Remove cuts that no longer exist in `self.cuts` - hist1d - .cuts - .retain(|cut| self.cuts.iter().any(|c| c.name() == cut.name())); - } - Config::Hist2D(hist2d) => { - for hist_cut in &mut hist2d.cuts { - if let Some(updated_cut) = - self.cuts.iter().find(|cut| cut.name() == hist_cut.name()) - { - // Replace the cut if the operation or content has changed - *hist_cut = updated_cut.clone(); - } - } - - // Remove cuts that no longer exist in `self.cuts` - hist2d - .cuts - .retain(|cut| self.cuts.iter().any(|c| c.name() == cut.name())); - } - } + configs: Configs::default(), + custom_scripts: CustomConfigs::default(), } } pub fn ui(&mut self, ui: &mut egui::Ui) { - self.histogram_script_ui(ui); + ui.heading("Histogram Script"); ui.separator(); egui::ScrollArea::vertical().show(ui, |ui| { - self.column_creation_ui(ui); - - ui.separator(); - - self.cut_ui(ui); + egui::CollapsingHeader::new("General") + .default_open(false) + .show(ui, |ui| { + self.configs.ui(ui); + }); ui.separator(); - self.histogram_ui(ui); + self.custom_scripts.ui(ui); }); - - self.verify_cuts(); - } - - fn save_histogram_script(&self) -> io::Result<()> { - if let Some(path) = FileDialog::new() - .set_title("Save Histogram Script") - .save_file() - { - let file = File::create(path)?; - let writer = BufWriter::new(file); - serde_json::to_writer(writer, &self) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) - } else { - Ok(()) // User canceled, return Ok - } } - fn load_histogram_script(&mut self) -> io::Result<()> { - if let Some(path) = FileDialog::new() - .set_title("Load Histogram Script") - .pick_file() - { - let file = File::open(path)?; - let reader = BufReader::new(file); - *self = serde_json::from_reader(reader) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - } - Ok(()) - } + pub fn add_histograms(&mut self, h: &mut Histogrammer, lf: LazyFrame, estimated_memory: f64) { + let active_custom_configs = self.custom_scripts.merge_active_configs(); - pub fn add_histograms(&mut self, h: &mut Histogrammer, lf: LazyFrame) { - let mut histo1d_configs = Vec::new(); - let mut histo2d_configs = Vec::new(); - - let range_re = regex::Regex::new(r"\{(\d+)-(\d+)\}").unwrap(); - // Regex for range pattern `{start-end}` - - let list_re = regex::Regex::new(r"\{([\d,]+)\}").unwrap(); - // Regex for discrete comma-separated values `{val1,val2,...}` - - for config in &self.hist_configs { - match config { - // 1D Histogram Configuration - Config::Hist1D(histo1d) => { - if histo1d.calculate { - if histo1d.name.contains("{}") { - // name has {} and column_name has a range pattern - if let Some(caps) = range_re.captures(&histo1d.column_name) { - let start: usize = caps[1].parse().unwrap(); - let end: usize = caps[2].parse().unwrap(); - - // Loop through start and end values - for i in start..=end { - let mut new_config = histo1d.clone(); - new_config.name = - histo1d.name.replace("{}", &i.to_string()).to_string(); - new_config.column_name = range_re - .replace(&histo1d.column_name, i.to_string()) - .to_string(); - histo1d_configs.push(new_config); - } - } - // name has {} and column_name has a list pattern - else if let Some(caps) = list_re.captures(&histo1d.column_name) { - // Split comma-separated values and loop over them - let values: Vec<&str> = caps[1].split(',').collect(); - for val in values { - let mut new_config = histo1d.clone(); - new_config.name = histo1d.name.replace("{}", val).to_string(); - new_config.column_name = - list_re.replace(&histo1d.column_name, val).to_string(); - histo1d_configs.push(new_config); - } - // Unsupported pattern - } else { - log::error!( - "Warning: Unsupported pattern for 1D histogram with name '{}', column '{}'", - histo1d.name, histo1d.column_name - ); - } - } else { - // No {} in name, but column_name has a range pattern - if let Some(caps) = range_re.captures(&histo1d.column_name) { - let start: usize = caps[1].parse().unwrap(); - let end: usize = caps[2].parse().unwrap(); - - for i in start..=end { - let mut new_config = histo1d.clone(); - new_config.column_name = range_re - .replace(&histo1d.column_name, i.to_string()) - .to_string(); - histo1d_configs.push(new_config); - } - } - // No {} in name, but column_name has a list pattern - else if let Some(caps) = list_re.captures(&histo1d.column_name) { - let values: Vec<&str> = caps[1].split(',').collect(); - for val in values { - let mut new_config = histo1d.clone(); - new_config.column_name = - list_re.replace(&histo1d.column_name, val).to_string(); - histo1d_configs.push(new_config); - } - // No {} in name or column_name i.e. a normal configuration - } else { - histo1d_configs.push(histo1d.clone()); - } - } - } - } - - // 2D Histogram Configuration - Config::Hist2D(histo2d) => { - if histo2d.calculate { - if histo2d.name.contains("{}") { - // Case 1: `{}` in `name`, `x_column_name` has a pattern - if let Some(caps) = range_re.captures(&histo2d.x_column_name) { - let start: usize = caps[1].parse().unwrap(); - let end: usize = caps[2].parse().unwrap(); - for i in start..=end { - let mut new_config = histo2d.clone(); - new_config.name = histo2d.name.replace("{}", &i.to_string()); - new_config.x_column_name = range_re - .replace(&histo2d.x_column_name, i.to_string()) - .to_string(); - new_config.y_column_name = histo2d.y_column_name.clone(); - histo2d_configs.push(new_config); - } - } else if let Some(caps) = list_re.captures(&histo2d.x_column_name) { - let values: Vec<&str> = caps[1].split(',').collect(); - for val in values { - let mut new_config = histo2d.clone(); - new_config.name = histo2d.name.replace("{}", val); - new_config.x_column_name = - list_re.replace(&histo2d.x_column_name, val).to_string(); - new_config.y_column_name = histo2d.y_column_name.clone(); - histo2d_configs.push(new_config); - } - } - // Case 2: `{}` in `name`, `y_column_name` has a pattern - else if let Some(caps) = range_re.captures(&histo2d.y_column_name) { - let start: usize = caps[1].parse().unwrap(); - let end: usize = caps[2].parse().unwrap(); - for i in start..=end { - let mut new_config = histo2d.clone(); - new_config.name = histo2d.name.replace("{}", &i.to_string()); - new_config.x_column_name = histo2d.x_column_name.clone(); - new_config.y_column_name = range_re - .replace(&histo2d.y_column_name, i.to_string()) - .to_string(); - histo2d_configs.push(new_config); - } - } else if let Some(caps) = list_re.captures(&histo2d.y_column_name) { - let values: Vec<&str> = caps[1].split(',').collect(); - for val in values { - let mut new_config = histo2d.clone(); - new_config.name = histo2d.name.replace("{}", val); - new_config.x_column_name = histo2d.x_column_name.clone(); - new_config.y_column_name = - list_re.replace(&histo2d.y_column_name, val).to_string(); - histo2d_configs.push(new_config); - } - } else { - log::error!( - "Warning: Unsupported pattern for 2D histogram with name '{}', x_column '{}', y_column '{}'", - histo2d.name, histo2d.x_column_name, histo2d.y_column_name - ); - } - } else { - // Static `name`, expand `x_column_name` or `y_column_name` with range or list patterns - if let Some(caps) = range_re.captures(&histo2d.x_column_name) { - let start: usize = caps[1].parse().unwrap(); - let end: usize = caps[2].parse().unwrap(); - for i in start..=end { - let mut new_config = histo2d.clone(); - new_config.x_column_name = range_re - .replace(&histo2d.x_column_name, i.to_string()) - .to_string(); - histo2d_configs.push(new_config); - } - } else if let Some(caps) = list_re.captures(&histo2d.x_column_name) { - let values: Vec<&str> = caps[1].split(',').collect(); - for val in values { - let mut new_config = histo2d.clone(); - new_config.x_column_name = - list_re.replace(&histo2d.x_column_name, val).to_string(); - histo2d_configs.push(new_config); - } - } else if let Some(caps) = range_re.captures(&histo2d.y_column_name) { - let start: usize = caps[1].parse().unwrap(); - let end: usize = caps[2].parse().unwrap(); - for i in start..=end { - let mut new_config = histo2d.clone(); - new_config.y_column_name = range_re - .replace(&histo2d.y_column_name, i.to_string()) - .to_string(); - histo2d_configs.push(new_config); - } - } else if let Some(caps) = list_re.captures(&histo2d.y_column_name) { - let values: Vec<&str> = caps[1].split(',').collect(); - for val in values { - let mut new_config = histo2d.clone(); - new_config.y_column_name = - list_re.replace(&histo2d.y_column_name, val).to_string(); - histo2d_configs.push(new_config); - } - } else { - histo2d_configs.push(histo2d.clone()); - } - } - } - } - } - } - - // Parse all conditions in histo1d_configs - for config in &mut histo1d_configs { - for cut in &mut config.cuts { - if let Cut::Cut1D(cut1d) = cut { - cut1d.parse_conditions(); // Pre-parse the conditions - } - } - } - - // Parse all conditions in histo2d_configs - for config in &mut histo2d_configs { - for cut in &mut config.cuts { - if let Cut::Cut1D(cut1d) = cut { - cut1d.parse_conditions(); // Pre-parse the conditions - } - } - } + let mut cloned_configs = self.configs.clone(); + cloned_configs.merge(active_custom_configs); + let merged_configs = cloned_configs; - // Pass expanded configurations to fill_histograms - h.fill_histograms( - histo1d_configs, - histo2d_configs, - &lf, - self.new_columns.clone(), - 10000000, - ); + h.fill_histograms(merged_configs.clone(), &lf, estimated_memory); } } diff --git a/src/histogram_scripter/manual_histogram_scripts.rs b/src/histogram_scripter/manual_histogram_scripts.rs deleted file mode 100644 index c085fa2..0000000 --- a/src/histogram_scripter/manual_histogram_scripts.rs +++ /dev/null @@ -1,395 +0,0 @@ -use crate::histoer::{configs::Config, cuts::Cut}; -use polars::prelude::*; -use std::f64::consts::PI; - -// pips1000(h, lf); -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo.... - -// sps_histograms(h, lf.clone()); - -// For 52Cr(d,pg)53Cr -// let det_0_timecut = TimeCut { mean: -1155.6, low: -1158.0, high: -1152.0}; -// let det_1_timecut = TimeCut { mean: -1153.9, low: -1159.0, high: -1147.0}; -// let det_2_timecut = TimeCut { mean: -1154.0, low: -1158.0, high: -1151.0}; -// let det_3_timecut = TimeCut { mean: -1152.0, low: -1158.0, high: -1148.0}; -// let det_4_timecut = TimeCut { mean: -1123.1, low: -1127.0, high: -1118.0}; - -// // These values were gain match to detector 0 -// // let det_0_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; -// // let det_1_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; -// // let det_2_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; -// // let det_3_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; -// // let det_4_gain_match_values = GainMatch { a: 0.0, b: 1.0, c: 0.0}; - -// let det_0_energy_calibration = EnergyCalibration { a: 0.0, b: 1.7551059351549314, c: -12.273506897222896, bins: 1024, range: (0.0, 16384.0) }; -// let det_1_energy_calibration = EnergyCalibration { a: 0.0, b: 1.9510278378962256, c: -16.0245754973971, bins: 1024, range: (0.0, 16384.0) }; -// let det_2_energy_calibration = EnergyCalibration { a: 0.0, b: 1.917190081718234, c: 16.430212777833802, bins: 1024, range: (0.0, 16384.0) }; -// let det_3_energy_calibration = EnergyCalibration { a: 0.0, b: 1.6931918955746692, c: 12.021258506937766, bins: 1024, range: (0.0, 16384.0) }; -// let det_4_energy_calibration = EnergyCalibration { a: 0.0, b: 1.6373533248536343, c: 13.091030061910748, bins: 1024, range: (0.0, 16384.0) }; - -// cebra(h, lf.clone(), 0, Some(det_0_timecut), None, Some(det_0_energy_calibration)); -// cebra(h, lf.clone(), 1, Some(det_1_timecut), None, Some(det_1_energy_calibration)); -// cebra(h, lf.clone(), 2, Some(det_2_timecut), None, Some(det_2_energy_calibration)); -// cebra(h, lf.clone(), 3, Some(det_3_timecut), None, Some(det_3_energy_calibration)); -// cebra(h, lf.clone(), 4, Some(det_4_timecut), None, Some(det_4_energy_calibration)); - -/// Helper function to get column names from a LazyFrame -pub fn get_column_names_from_lazyframe(lazyframe: &LazyFrame) -> Vec { - let lf: LazyFrame = lazyframe.clone().limit(1); - let df: DataFrame = lf.collect().unwrap(); - let columns: Vec = df - .get_column_names_owned() - .into_iter() - .map(|name| name.to_string()) - .collect(); - - columns -} - -#[rustfmt::skip] -#[allow(clippy::all)] -pub fn sps_histograms() -> (Vec<(String, String)>, Vec, Vec) { - - let mut new_columns = vec![]; - new_columns.push(("( DelayFrontRightEnergy + DelayFrontLeftEnergy ) / 2.0".into(), "DelayFrontAverageEnergy".into())); - new_columns.push(("( DelayBackRightEnergy + DelayBackLeftEnergy ) / 2.0".into(), "DelayBackAverageEnergy".into())); - new_columns.push(("DelayFrontLeftTime - AnodeFrontTime".into(), "DelayFrontLeftTime_AnodeFrontTime".into())); - new_columns.push(("DelayFrontRightTime - AnodeFrontTime".into(), "DelayFrontRightTime_AnodeFrontTime".into())); - new_columns.push(("DelayBackLeftTime - AnodeFrontTime".into(), "DelayBackLeftTime_AnodeFrontTime".into())); - new_columns.push(("DelayBackRightTime - AnodeFrontTime".into(), "DelayBackRightTime_AnodeFrontTime".into())); - new_columns.push(("DelayFrontLeftTime - AnodeBackTime".into(), "DelayFrontLeftTime_AnodeBackTime".into())); - new_columns.push(("DelayFrontRightTime - AnodeBackTime".into(), "DelayFrontRightTime_AnodeBackTime".into())); - new_columns.push(("DelayBackLeftTime - AnodeBackTime".into(), "DelayBackLeftTime_AnodeBackTime".into())); - new_columns.push(("DelayBackRightTime - AnodeBackTime".into(), "DelayBackRightTime_AnodeBackTime".into())); - new_columns.push(("AnodeFrontTime - AnodeBackTime".into(), "AnodeFrontTime_AnodeBackTime".into())); - new_columns.push(("AnodeBackTime - AnodeFrontTime".into(), "AnodeBackTime_AnodeFrontTime".into())); - new_columns.push(("AnodeFrontTime - ScintLeftTime".into(), "AnodeFrontTime_ScintLeftTime".into())); - new_columns.push(("AnodeBackTime - ScintLeftTime".into(), "AnodeBackTime_ScintLeftTime".into())); - new_columns.push(("DelayFrontLeftTime - ScintLeftTime".into(), "DelayFrontLeftTime_ScintLeftTime".into())); - new_columns.push(("DelayFrontRightTime - ScintLeftTime".into(), "DelayFrontRightTime_ScintLeftTime".into())); - new_columns.push(("DelayBackLeftTime - ScintLeftTime".into(), "DelayBackLeftTime_ScintLeftTime".into())); - new_columns.push(("DelayBackRightTime - ScintLeftTime".into(), "DelayBackRightTime_ScintLeftTime".into())); - new_columns.push(("ScintRightTime - ScintLeftTime".into(), "ScintRightTime_ScintLeftTime".into())); - - let mut cuts = vec![]; - - let bothplanes_cut = Cut::new_1d("Both Planes", "X2 != -1e6 && X1 != -1e6"); - cuts.push(bothplanes_cut.clone()); - - let only_x1_plane_cut = Cut::new_1d("Only X1 Plane", "X1 != -1e6 && X2 == -1e6"); - cuts.push(only_x1_plane_cut.clone()); - - let only_x2_plane_cut = Cut::new_1d("Only X2 Plane", "X2 != -1e6 && X1 == -1e6"); - cuts.push(only_x2_plane_cut.clone()); - - let mut histograms = vec![]; - - let fp_range = (-300.0, 300.0); - let fp_bins = 600; - - let range = (0.0, 4096.0); - let bins = 512; - - // Focal plane histograms - histograms.push(Config::new_1d("SE-SPS/Focal Plane/X1", "X1", fp_range, fp_bins, None)); - histograms.push(Config::new_1d("SE-SPS/Focal Plane/X2", "X2", fp_range, fp_bins, None)); - histograms.push(Config::new_1d("SE-SPS/Focal Plane/Xavg", "Xavg", fp_range, fp_bins, None)); - histograms.push(Config::new_2d("SE-SPS/Focal Plane/X2 v X1", "X1", "X2", fp_range, fp_range, (fp_bins, fp_bins), None)); - histograms.push(Config::new_2d("SE-SPS/Focal Plane/Theta v Xavg", "Xavg", "Theta", fp_range, (0.0, PI), (fp_bins, fp_bins), None)); - histograms.push(Config::new_2d("SE-SPS/Focal Plane/Rays", "X", "Z", fp_range, (-50.0, 50.0), (fp_bins, 100), None)); - - let cut_bothplanes = Some(vec![bothplanes_cut.clone()]); - let cut_only_x1_plane = Some(vec![only_x1_plane_cut.clone()]); - let cut_only_x2_plane = Some(vec![only_x2_plane_cut.clone()]); - - histograms.push(Config::new_1d("SE-SPS/Focal Plane/Checks/Xavg", "Xavg", fp_range, fp_bins, None)); - histograms.push(Config::new_1d("SE-SPS/Focal Plane/Checks/Raw- X1", "X1", fp_range, fp_bins, None)); - histograms.push(Config::new_1d("SE-SPS/Focal Plane/Checks/Both Planes- X1", "X1", fp_range, fp_bins, cut_bothplanes.clone())); - histograms.push(Config::new_1d("SE-SPS/Focal Plane/Checks/Only 1 Plane- X1", "X1", fp_range, fp_bins, cut_only_x1_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Focal Plane/Checks/Raw- X2", "X2", fp_range, fp_bins, None)); - histograms.push(Config::new_1d("SE-SPS/Focal Plane/Checks/Both Planes- X2", "X2", fp_range, fp_bins, cut_bothplanes.clone())); - histograms.push(Config::new_1d("SE-SPS/Focal Plane/Checks/Only 1 Plane- X2", "X2", fp_range, fp_bins, cut_only_x2_plane.clone())); - - // Particle Identification histograms - histograms.push(Config::new_2d("SE-SPS/Particle Identification/AnodeBack v ScintLeft", "ScintLeftEnergy", "AnodeBackEnergy", range, range, (bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification/AnodeFront v ScintLeft", "ScintLeftEnergy", "AnodeFrontEnergy", range, range, (bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification/Cathode v ScintLeft", "ScintLeftEnergy", "CathodeEnergy", range, range, (bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification/AnodeBack v ScintRight", "ScintRightEnergy", "AnodeBackEnergy", range, range, (bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification/AnodeFront v ScintRight", "ScintRightEnergy", "AnodeFrontEnergy", range, range, (bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification/Cathode v ScintRight", "ScintRightEnergy", "CathodeEnergy", range, range, (bins,bins), None)); - - // Particle Identification vs Focal plane histograms - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/ScintLeft v X1", "X1", "ScintLeftEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/ScintLeft v X2", "X2", "ScintLeftEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/ScintLeft v Xavg", "Xavg", "ScintLeftEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/ScintRight v X1", "X1", "ScintRightEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/ScintRight v X2", "X2", "ScintRightEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/ScintRight v Xavg", "Xavg", "ScintRightEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/AnodeBack v X1", "X1", "AnodeBackEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/AnodeBack v X2", "X2", "AnodeBackEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/AnodeBack v Xavg", "Xavg", "AnodeBackEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/AnodeFront v X1", "X1", "AnodeFrontEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/AnodeFront v X2", "X2", "AnodeFrontEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/AnodeFront v Xavg", "Xavg", "AnodeFrontEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/Cathode v X1", "X1", "CathodeEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/Cathode v X2", "X2", "CathodeEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Particle Identification v Focal Plane/Cathode v Xavg", "Xavg", "CathodeEnergy", fp_range, range, (fp_bins,bins), None)); - - // Delay lines vs Focal plane histograms - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontRight v X1", "X1", "DelayFrontRightEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontLeft v X1", "X1", "DelayFrontLeftEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayBackRight v X2", "X2", "DelayBackRightEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayBackLeft v X2", "X2", "DelayBackLeftEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayBackRight v Xavg", "Xavg", "DelayBackRightEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayBackLeft v Xavg", "Xavg", "DelayBackLeftEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontRight v Xavg", "Xavg", "DelayFrontRightEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontLeft v Xavg", "Xavg", "DelayFrontLeftEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayBackRight v X1", "X1", "DelayBackRightEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayBackLeft v X1", "X1", "DelayBackLeftEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontRight v X2", "X2", "DelayFrontRightEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/DelayFrontLeft v X2", "X2", "DelayFrontLeftEnergy", fp_range, range, (fp_bins,bins), None)); - - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayFrontAverage v X1", "X1", "DelayFrontAverageEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayBackAverage v X1", "X1", "DelayBackAverageEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayFrontAverage v X2", "X2", "DelayFrontAverageEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayBackAverage v X2", "X2", "DelayBackAverageEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayFrontAverage v Xavg", "Xavg", "DelayFrontAverageEnergy", fp_range, range, (fp_bins,bins), None)); - histograms.push(Config::new_2d("SE-SPS/Delay Lines v Focal Plane/Averages/DelayBackAverage v Xavg", "Xavg", "DelayBackAverageEnergy", fp_range, range, (fp_bins,bins), None)); - - - // Delay timing relative to anodes histograms - let valid_sps_timing = Cut::new_1d("Valid SPS Timing", "AnodeBackTime != -1e6 && ScintLeftTime != -1e6"); - cuts.push(valid_sps_timing.clone()); - - let cut_timing = Some(vec![valid_sps_timing.clone()]); - - histograms.push(Config::new_1d("SE-SPS/Timing/AnodeFrontTime-AnodeBackTime", "AnodeFrontTime_AnodeBackTime", (-3000.0, 3000.0), 1000, cut_timing.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/AnodeBackTime-AnodeFrontTime", "AnodeBackTime_AnodeFrontTime", (-3000.0, 3000.0), 1000, cut_timing.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/AnodeFrontTime-ScintLeftTime", "AnodeFrontTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/AnodeBackTime-ScintLeftTime", "AnodeBackTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/DelayFrontLeftTime-ScintLeftTime", "DelayFrontLeftTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/DelayFrontRightTime-ScintLeftTime", "DelayFrontRightTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/DelayBackLeftTime-ScintLeftTime", "DelayBackLeftTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/DelayBackRightTime-ScintLeftTime", "DelayBackRightTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/ScintRightTime-ScintLeftTime", "ScintRightTime_ScintLeftTime", (-3000.0, 3000.0), 1000, cut_timing.clone())); - histograms.push(Config::new_2d("SE-SPS/Timing/ScintTimeDif v Xavg", "Xavg", "ScintRightTime_ScintLeftTime", fp_range, (-3200.0, 3200.0), (fp_bins, 12800), cut_timing.clone())); - - - histograms.push(Config::new_1d("SE-SPS/Timing/Both Planes/DelayFrontLeftTime-AnodeFrontTime", "DelayFrontLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_bothplanes.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Both Planes/DelayFrontRightTime-AnodeFrontTime", "DelayFrontRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_bothplanes.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Both Planes/DelayBackLeftTime-AnodeBackTime", "DelayBackLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_bothplanes.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Both Planes/DelayBackRightTime-AnodeBackTime", "DelayBackRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_bothplanes.clone())); - - histograms.push(Config::new_1d("SE-SPS/Timing/Only X1 Plane/DelayFrontLeftTime-AnodeFrontTime", "DelayFrontLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X1 Plane/DelayFrontRightTime-AnodeFrontTime", "DelayFrontRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X1 Plane/DelayBackLeftTime-AnodeFrontTime", "DelayBackLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X1 Plane/DelayBackRightTime-AnodeFrontTime", "DelayBackRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X1 Plane/DelayFrontLeftTime-AnodeBackTime", "DelayFrontLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X1 Plane/DelayFrontRightTime-AnodeBackTime", "DelayFrontRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X1 Plane/DelayBackLeftTime-AnodeBackTime", "DelayBackLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X1 Plane/DelayBackRightTime-AnodeBackTime", "DelayBackRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x1_plane.clone())); - - histograms.push(Config::new_1d("SE-SPS/Timing/Only X2 Plane/DelayFrontLeftTime-AnodeFrontTime", "DelayFrontLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X2 Plane/DelayFrontRightTime-AnodeFrontTime", "DelayFrontRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X2 Plane/DelayBackLeftTime-AnodeFrontTime", "DelayBackLeftTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X2 Plane/DelayBackRightTime-AnodeFrontTime", "DelayBackRightTime_AnodeFrontTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X2 Plane/DelayFrontLeftTime-AnodeBackTime", "DelayFrontLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X2 Plane/DelayFrontRightTime-AnodeBackTime", "DelayFrontRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X2 Plane/DelayBackLeftTime-AnodeBackTime", "DelayBackLeftTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone())); - histograms.push(Config::new_1d("SE-SPS/Timing/Only X2 Plane/DelayBackRightTime-AnodeBackTime", "DelayBackRightTime_AnodeBackTime", (-4000.0, 4000.0), 8000, cut_only_x2_plane.clone())); - - (new_columns, histograms, cuts) -} -/* -#[rustfmt::skip] -#[allow(clippy::all)] -pub fn pips1000(h: &mut Histogrammer, lf: LazyFrame) { - let lf_pips = lf.with_columns( vec![ - // ( ( col("PIPS1000Energy") - col("PIPS1000Short") )/ col("PIPS1000Energy") ).alias("PIPS1000PSD"), - (lit(-1.77049e-06)*col("PIPS1000Energy")*col("PIPS1000Energy") + lit(0.544755003513083)*col("PIPS1000Energy") + lit(-1.36822594543883)).alias("PIPS1000EnergyCalibrated") ] - ); - - h.add_fill_hist1d("PIPS1000/Energy", &lf_pips, "PIPS1000Energy", 16384, (0.0, 16384.0)); - // h.add_fill_hist2d("PIPS1000: PSD", &lf_pips, "PIPS1000Energy", "PIPS1000PSD", (512, 500), (range, (0.0, 1.0))); - h.add_fill_hist1d("PIPS1000/Energy Calibrated", &lf_pips, "PIPS1000EnergyCalibrated", 600, (0.0, 1200.0)); - -} - -pub struct TimeCut { - pub mean: f64, - pub low: f64, - pub high: f64, -} - -pub struct GainMatch { - pub a: f64, - pub b: f64, - pub c: f64, -} - -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)) - ) - } 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)); - } - - 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); - - 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)); - - 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)); - } - } - }; - -} - -#[rustfmt::skip] -#[allow(clippy::all)] -pub fn catrina(h: &mut Histogrammer, lf: LazyFrame, detector_number: usize) { - let i = detector_number; - - h.add_fill_hist1d(&format!("Catrina/CATRINA{i}/Energy"), &lf, &format!("CATRINA{i}Energy"), 4096, range); - h.add_fill_hist2d(&format!("Catrina/CATRINA{i}/PSD vs Energy"), &lf, &format!("CATRINA{i}Energy"), &format!("CATRINA{i}PSD"), (512, 500), (range, (0.0, 1.0))); -} - - -*/ diff --git a/src/histogram_scripter/mod.rs b/src/histogram_scripter/mod.rs index 4fe5bd7..0744051 100644 --- a/src/histogram_scripter/mod.rs +++ b/src/histogram_scripter/mod.rs @@ -1,2 +1,2 @@ +pub mod custom_scripts; pub mod histogram_script; -pub mod manual_histogram_scripts; diff --git a/src/ui/app.rs b/src/ui/app.rs index ca16883..9fa5598 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,5 +1,4 @@ use crate::util::processer::Processor; - #[derive(serde::Deserialize, serde::Serialize)] #[serde(default)] pub struct Spectrix { @@ -16,14 +15,15 @@ impl Default for Spectrix { impl Spectrix { pub fn new(cc: &eframe::CreationContext<'_>) -> Self { - // Load previous app state (if any). - // Note that you must enable the `persistence` feature for this to work. if let Some(storage) = cc.storage { return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); } - Default::default() } + + pub fn reset_to_default(&mut self) { + *self = Default::default(); + } } impl eframe::App for Spectrix { @@ -47,13 +47,13 @@ impl eframe::App for Spectrix { ui.separator(); - if ui.button("Reset").clicked() { - self.processor.reset(); - } + self.processor.histogrammer.menu_ui(ui); - ui.separator(); + ui.add_space(ui.available_width() - 50.0); - self.processor.histogrammer.menu_ui(ui); + if ui.button("Reset").clicked() { + self.reset_to_default(); + } }); }); diff --git a/src/util/egui_file_dialog.rs b/src/util/egui_file_dialog.rs deleted file mode 100644 index bdefea6..0000000 --- a/src/util/egui_file_dialog.rs +++ /dev/null @@ -1,1210 +0,0 @@ -use std::{ - cmp, - cmp::Ordering, - env, - fmt::Debug, - fs, - fs::FileType, - io::Error, - ops::Deref, - path::{Path, PathBuf}, -}; - -use egui::{ - Align2, Context, Id, Key, Layout, Pos2, RichText, ScrollArea, TextEdit, Ui, Vec2, Window, -}; - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -/// Dialog state. -pub enum State { - /// Is currently visible. - Open, - /// Is currently not visible. - Closed, - /// Was canceled. - Cancelled, - /// File was selected. - Selected, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -/// Dialog type. -pub enum DialogType { - SelectFolder, - OpenFile, - SaveFile, -} - -/// `egui` component that represents `OpenFileDialog` or `SaveFileDialog`. -pub struct FileDialog { - /// Current opened path. - path: PathBuf, - - /// Editable field with path. - path_edit: String, - - /// Selected file path (single select mode). - selected_file: Option, - - /// Editable field with filename. - filename_edit: String, - - /// Dialog title text - title: String, - - /// Open button text - open_button_text: &'static str, - - /// Save button text - save_button_text: &'static str, - - /// Cancel button text - cancel_button_text: &'static str, - - /// New Folder button text - new_folder_button_text: &'static str, - - /// New Folder name text - new_folder_name_text: &'static str, - - /// Rename button text - rename_button_text: &'static str, - - /// Refresh button hover text - refresh_button_hover_text: &'static str, - - /// Parent Folder button hover text - parent_folder_button_hover_text: &'static str, - - /// File label text - file_label_text: &'static str, - - /// Show Hidden checkbox text - show_hidden_checkbox_text: &'static str, - - /// Files in directory. - files: Result, Error>, - - /// Current dialog state. - state: State, - - /// Dialog type. - dialog_type: DialogType, - - id: Option, - current_pos: Option, - default_pos: Option, - default_size: Vec2, - anchor: Option<(Align2, Vec2)>, - show_files_filter: Filter, - filename_filter: Filter, - range_start: Option, - resizable: bool, - rename: bool, - new_folder: bool, - multi_select_enabled: bool, - keep_on_top: bool, - show_system_files: bool, - - /// Show drive letters on Windows. - #[cfg(windows)] - show_drives: bool, - - /// Show hidden files on unix systems. - #[cfg(unix)] - show_hidden: bool, -} - -impl Debug for FileDialog { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut dbg = f.debug_struct("FileDialog"); - let dbg = dbg - .field("path", &self.path) - .field("path_edit", &self.path_edit) - .field("selected_file", &self.selected_file) - .field("filename_edit", &self.filename_edit) - .field("files", &self.files) - .field("state", &self.state) - .field("dialog_type", &self.dialog_type) - .field("current_pos", &self.current_pos) - .field("default_pos", &self.default_pos) - .field("default_size", &self.default_size) - .field("anchor", &self.anchor) - .field("resizable", &self.resizable) - .field("rename", &self.rename) - .field("new_folder", &self.new_folder) - .field("multi_select", &self.multi_select_enabled) - .field("range_start", &self.range_start) - .field("keep_on_top", &self.keep_on_top) - .field("show_system_files", &self.show_system_files); - - // Closures don't implement std::fmt::Debug. - // let dbg = dbg - // .field("shown_files_filter", &self.shown_files_filter) - // .field("filename_filter", &self.filename_filter); - - #[cfg(unix)] - let dbg = dbg.field("show_hidden", &self.show_hidden); - - #[cfg(windows)] - let dbg = dbg.field("show_drives", &self.show_drives); - - dbg.finish() - } -} - -/// Function that returns `true` if the path is accepted. -pub type Filter = Box::Target) -> bool + Send + Sync + 'static>; - -impl FileDialog { - /// Create dialog that prompts the user to select a folder. - pub fn select_folder(initial_path: Option) -> Self { - FileDialog::new(DialogType::SelectFolder, initial_path) - } - - /// Create dialog that prompts the user to open a file. - pub fn open_file(initial_path: Option) -> Self { - FileDialog::new(DialogType::OpenFile, initial_path) - } - - /// Create dialog that prompts the user to save a file. - pub fn save_file(initial_path: Option) -> Self { - FileDialog::new(DialogType::SaveFile, initial_path) - } - - /// Constructs new file dialog. If no `initial_path` is passed,`env::current_dir` is used. - fn new(dialog_type: DialogType, initial_path: Option) -> Self { - let mut path = initial_path.unwrap_or_else(|| env::current_dir().unwrap_or_default()); - let mut filename_edit = String::new(); - let info = FileInfo::new(path.clone()); - - if info.is_file() { - assert!(dialog_type != DialogType::SelectFolder); - filename_edit = get_file_name(&info).to_string(); - path.pop(); - } - - let path_edit = path.to_str().unwrap_or_default().to_string(); - Self { - path, - path_edit, - selected_file: None, - filename_edit, - title: match dialog_type { - DialogType::SelectFolder => "📁 Select Folder", - DialogType::OpenFile => "📂 Open File", - DialogType::SaveFile => "💾 Save File", - } - .to_string(), - open_button_text: "Open", - save_button_text: "Save", - cancel_button_text: "Cancel", - new_folder_button_text: "New Folder", - new_folder_name_text: "New folder", - rename_button_text: "Rename", - refresh_button_hover_text: "Refresh", - parent_folder_button_hover_text: "Parent Folder", - file_label_text: "File:", - show_hidden_checkbox_text: "Show Hidden", - files: Ok(Vec::new()), - state: State::Closed, - dialog_type, - - id: None, - current_pos: None, - default_pos: None, - default_size: egui::vec2(512.0, 512.0), - anchor: None, - show_files_filter: Box::new(|_| true), - filename_filter: Box::new(|_| true), - resizable: true, - rename: true, - new_folder: true, - - #[cfg(windows)] - show_drives: true, - - #[cfg(unix)] - show_hidden: false, - multi_select_enabled: false, - range_start: None, - keep_on_top: false, - show_system_files: false, - } - } - - /// Set the default file name. - pub fn default_filename(mut self, filename: impl Into) -> Self { - self.filename_edit = filename.into(); - self - } - - /// Set the window title text. - pub fn title(mut self, title: &str) -> Self { - self.title = match self.dialog_type { - DialogType::SelectFolder => "📁 ", - DialogType::OpenFile => "📂 ", - DialogType::SaveFile => "💾 ", - } - .to_string() - + title; - self - } - - /// Set the open button text. - pub fn open_button_text(mut self, text: &'static str) -> Self { - self.open_button_text = text; - self - } - - /// Set the save button text. - pub fn save_button_text(mut self, text: &'static str) -> Self { - self.save_button_text = text; - self - } - - /// Set the cancel button text. - pub fn cancel_button_text(mut self, text: &'static str) -> Self { - self.cancel_button_text = text; - self - } - - /// Set the new folder button text. - pub fn new_folder_button_text(mut self, text: &'static str) -> Self { - self.new_folder_button_text = text; - self - } - - /// Set the new folder name text. - pub fn new_folder_name_text(mut self, text: &'static str) -> Self { - self.new_folder_name_text = text; - self - } - - /// Set the refresh button hover text. - pub fn refresh_button_hover_text(mut self, text: &'static str) -> Self { - self.refresh_button_hover_text = text; - self - } - - /// Set the parent folder button hover text. - pub fn parent_folder_button_hover_text(mut self, text: &'static str) -> Self { - self.parent_folder_button_hover_text = text; - self - } - - /// Set the rename button text. - pub fn rename_button_text(mut self, text: &'static str) -> Self { - self.rename_button_text = text; - self - } - - /// Set the file label text. - pub fn file_label_text(mut self, text: &'static str) -> Self { - self.file_label_text = text; - self - } - - /// Set the show hidden checkbox text. - pub fn show_hidden_checkbox_text(mut self, text: &'static str) -> Self { - self.show_hidden_checkbox_text = text; - self - } - - /// Set the window ID. - pub fn id(mut self, id: impl Into) -> Self { - self.id = Some(id.into()); - self - } - - /// Set the window anchor. - pub fn anchor(mut self, align: Align2, offset: impl Into) -> Self { - self.anchor = Some((align, offset.into())); - self - } - - /// Set the window's current position. - pub fn current_pos(mut self, current_pos: impl Into) -> Self { - self.current_pos = Some(current_pos.into()); - self - } - - /// Set the window's default position. - pub fn default_pos(mut self, default_pos: impl Into) -> Self { - self.default_pos = Some(default_pos.into()); - self - } - - /// Set the window's default size. - pub fn default_size(mut self, default_size: impl Into) -> Self { - self.default_size = default_size.into(); - self - } - - /// Enable/disable resizing the window. Default is `true`. - pub fn resizable(mut self, resizable: bool) -> Self { - self.resizable = resizable; - self - } - - /// Show the Rename button. Default is `true`. - pub fn show_rename(mut self, rename: bool) -> Self { - self.rename = rename; - self - } - - /// Show the New Folder button. Default is `true`. - pub fn show_new_folder(mut self, new_folder: bool) -> Self { - self.new_folder = new_folder; - self - } - - pub fn multi_select(mut self, multi_select: bool) -> Self { - self.multi_select_enabled = multi_select; - self - } - - pub fn has_multi_select(&self) -> bool { - self.multi_select_enabled - } - - /// Show the mapped drives on Windows. Default is `true`. - #[cfg(windows)] - pub fn show_drives(mut self, drives: bool) -> Self { - self.show_drives = drives; - self - } - - /// Set a function to filter listed files. - pub fn show_files_filter(mut self, filter: Filter) -> Self { - self.show_files_filter = filter; - self - } - - /// Set a function to filter the selected filename. - pub fn filename_filter(mut self, filter: Filter) -> Self { - self.filename_filter = filter; - self - } - - /// Set to true in order to keep this window on top of other windows. Default is `false`. - pub fn keep_on_top(mut self, keep_on_top: bool) -> Self { - self.keep_on_top = keep_on_top; - self - } - - /// Set to true in order to show system files. Default is `false`. - pub fn show_system_files(mut self, show_system_files: bool) -> Self { - self.show_system_files = show_system_files; - self - } - - /// Get the dialog type. - pub fn dialog_type(&self) -> DialogType { - self.dialog_type - } - - /// Get the window's visibility. - pub fn visible(&self) -> bool { - self.state == State::Open - } - - /// Opens the dialog. - pub fn open(&mut self) { - self.state = State::Open; - self.refresh(); - } - - /// Resulting file path. - pub fn path(&self) -> Option<&Path> { - self.selected_file.as_ref().map(|info| info.path.as_path()) - } - - /// Retrieves multi selection as a vector. - pub fn selection(&self) -> Vec<&Path> { - match self.files { - Ok(ref files) => files - .iter() - .filter_map(|info| { - if info.selected { - Some(info.path.as_path()) - } else { - None - } - }) - .collect(), - Err(_) => Vec::new(), - } - } - - /// Currently mounted directory that is being shown in the dialog box - pub fn directory(&self) -> &Path { - self.path.as_path() - } - - /// Set the dialog's current opened path - pub fn set_path(&mut self, path: impl Into) { - self.path = path.into(); - self.refresh(); - } - - /// Dialog state. - pub fn state(&self) -> State { - self.state - } - - /// Returns true, if the file selection was confirmed. - pub fn selected(&self) -> bool { - self.state == State::Selected - } - - fn open_selected(&mut self) { - if let Some(info) = &self.selected_file { - if info.is_dir() { - self.set_path(info.path.clone()); - } else if self.dialog_type == DialogType::OpenFile { - self.confirm(); - } - } else if self.multi_select_enabled && self.dialog_type == DialogType::OpenFile { - self.confirm(); - } - } - - fn confirm(&mut self) { - self.state = State::Selected; - } - - fn refresh(&mut self) { - self.files = self.read_folder(); - self.path_edit = String::from(self.path.to_str().unwrap_or_default()); - self.select(None); - self.selected_file = None; - } - - fn select(&mut self, file: Option) { - if let Some(info) = &file { - if !info.is_dir() { - get_file_name(info).clone_into(&mut self.filename_edit); - } - } - self.selected_file = file; - } - - fn select_reset_multi(&mut self, idx: usize) { - if let Ok(files) = &mut self.files { - let selected_val = files[idx].selected; - for file in files.iter_mut() { - file.selected = false; - } - files[idx].selected = !selected_val; - self.range_start = Some(idx); - } - } - - fn select_switch_multi(&mut self, idx: usize) { - if let Ok(files) = &mut self.files { - files[idx].selected = !files[idx].selected; - if files[idx].selected { - self.range_start = Some(idx); - } else { - self.range_start = None; - } - } else { - self.range_start = None; - } - } - - fn select_range(&mut self, idx: usize) { - if let Ok(files) = &mut self.files { - if let Some(range_start) = self.range_start { - let range = cmp::min(idx, range_start)..=cmp::max(idx, range_start); - for i in range { - files[i].selected = true; - } - } - } - } - - fn can_save(&self) -> bool { - !self.filename_edit.is_empty() && (self.filename_filter)(self.filename_edit.as_str()) - } - - fn can_open(&self) -> bool { - if self.multi_select_enabled { - if let Ok(files) = &self.files { - for file in files { - if file.selected && (self.filename_filter)(get_file_name(file)) { - return true; - } - } - } - false - } else { - !self.filename_edit.is_empty() && (self.filename_filter)(self.filename_edit.as_str()) - } - } - - fn can_rename(&self) -> bool { - if !self.filename_edit.is_empty() { - if let Some(file) = &self.selected_file { - return get_file_name(file) != self.filename_edit; - } - } - false - } - - /// Shows the dialog if it is open. It is also responsible for state management. - /// Should be called every ui update. - pub fn show(&mut self, ctx: &Context) -> &Self { - self.state = match self.state { - State::Open => { - if ctx.input(|state| state.key_pressed(Key::Escape)) { - self.state = State::Cancelled; - } - - let mut is_open = true; - self.ui(ctx, &mut is_open); - match is_open { - true => self.state, - false => State::Cancelled, - } - } - _ => State::Closed, - }; - - self - } - - fn ui(&mut self, ctx: &Context, is_open: &mut bool) { - let mut window = Window::new(RichText::new(&self.title).strong()) - .open(is_open) - .default_size(self.default_size) - .resizable(self.resizable) - .collapsible(false); - - if let Some(id) = self.id { - window = window.id(id); - } - - if let Some((align, offset)) = self.anchor { - window = window.anchor(align, offset); - } - - if let Some(current_pos) = self.current_pos { - window = window.current_pos(current_pos); - } - - if let Some(default_pos) = self.default_pos { - window = window.default_pos(default_pos); - } - - window.show(ctx, |ui| { - if self.keep_on_top { - ui.ctx().move_to_top(ui.layer_id()); - } - self.ui_in_window(ui) - }); - } - - fn ui_in_window(&mut self, ui: &mut Ui) { - enum Command { - Cancel, - CreateDirectory, - Folder, - Open(FileInfo), - OpenSelected, - BrowseDirectory(FileInfo), - Refresh, - Rename(PathBuf, PathBuf), - Save(FileInfo), - Select(FileInfo), - MultiSelectRange(usize), - MultiSelect(usize), - MultiSelectSwitch(usize), - UpDirectory, - } - let mut command: Option = None; - - // Top directory field with buttons. - egui::TopBottomPanel::top("egui_file_top").show_inside(ui, |ui| { - ui.horizontal(|ui| { - ui.add_enabled_ui(self.path.parent().is_some(), |ui| { - let response = ui - .button("⬆") - .on_hover_text(self.parent_folder_button_hover_text); - if response.clicked() { - command = Some(Command::UpDirectory); - } - }); - ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| { - let response = ui.button("⟲").on_hover_text(self.refresh_button_hover_text); - if response.clicked() { - command = Some(Command::Refresh); - } - - let response = ui.add_sized( - ui.available_size(), - TextEdit::singleline(&mut self.path_edit), - ); - - if response.lost_focus() { - let path = PathBuf::from(&self.path_edit); - command = Some(Command::Open(FileInfo::new(path))); - } - }); - }); - ui.add_space(ui.spacing().item_spacing.y); - }); - - // Bottom file field. - egui::TopBottomPanel::bottom("egui_file_bottom").show_inside(ui, |ui| { - ui.add_space(ui.spacing().item_spacing.y * 2.0); - ui.horizontal(|ui| { - ui.label(self.file_label_text); - ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| { - if self.new_folder && ui.button(self.new_folder_button_text).clicked() { - command = Some(Command::CreateDirectory); - } - - if self.rename { - ui.add_enabled_ui(self.can_rename(), |ui| { - if ui.button(self.rename_button_text).clicked() { - if let Some(from) = self.selected_file.clone() { - let to = from.path.with_file_name(&self.filename_edit); - command = Some(Command::Rename(from.path, to)); - } - } - }); - } - - let response = ui.add_sized( - ui.available_size(), - TextEdit::singleline(&mut self.filename_edit), - ); - - if response.lost_focus() { - let ctx = response.ctx; - let enter_pressed = ctx.input(|state| state.key_pressed(Key::Enter)); - - if enter_pressed && (self.filename_filter)(self.filename_edit.as_str()) { - let path = self.path.join(&self.filename_edit); - match self.dialog_type { - DialogType::SelectFolder => command = Some(Command::Folder), - DialogType::OpenFile => { - if path.exists() { - command = Some(Command::Open(FileInfo::new(path))); - } - } - DialogType::SaveFile => { - let file_info = FileInfo::new(path); - command = Some(match file_info.is_dir() { - true => Command::Open(file_info), - false => Command::Save(file_info), - }); - } - } - } - } - }); - }); - - ui.add_space(ui.spacing().item_spacing.y); - - // Confirm, Cancel buttons. - ui.horizontal(|ui| { - match self.dialog_type { - DialogType::SelectFolder => { - ui.horizontal(|ui| { - if ui.button(self.open_button_text).clicked() { - command = Some(Command::Folder); - }; - }); - } - DialogType::OpenFile => { - ui.horizontal(|ui| { - if !self.can_open() { - ui.disable(); - } - - if ui.button(self.open_button_text).clicked() { - command = Some(Command::OpenSelected); - }; - }); - } - DialogType::SaveFile => { - let should_open_directory = match &self.selected_file { - Some(file) => file.is_dir(), - None => false, - }; - - if should_open_directory { - if ui.button(self.open_button_text).clicked() { - command = Some(Command::OpenSelected); - }; - } else { - ui.horizontal(|ui| { - if !self.can_save() { - ui.disable(); - } - - if ui.button(self.save_button_text).clicked() { - let filename = &self.filename_edit; - let path = self.path.join(filename); - command = Some(Command::Save(FileInfo::new(path))); - }; - }); - } - } - } - - if ui.button(self.cancel_button_text).clicked() { - command = Some(Command::Cancel); - } - - #[cfg(unix)] - ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| { - if ui - .checkbox(&mut self.show_hidden, self.show_hidden_checkbox_text) - .changed() - { - self.refresh(); - } - }); - }); - }); - - // File list. - egui::CentralPanel::default().show_inside(ui, |ui| { - ScrollArea::vertical().show_rows( - ui, - ui.text_style_height(&egui::TextStyle::Body), - self.files.as_ref().map_or(0, |files| files.len()), - |ui, range| match self.files.as_ref() { - Ok(files) => { - ui.with_layout(ui.layout().with_cross_justify(true), |ui| { - let selected = self.selected_file.as_ref().map(|info| &info.path); - let range_start = range.start; - - for (n, info) in files[range].iter().enumerate() { - let idx = n + range_start; - let label = match info.is_dir() { - true => "🗀 ", - false => "🗋 ", - } - .to_string() - + get_file_name(info); - - let is_selected = if self.multi_select_enabled { - files[idx].selected - } else { - Some(&info.path) == selected - }; - let response = ui.selectable_label(is_selected, label); - if response.clicked() { - if self.multi_select_enabled { - if ui.input(|i| i.modifiers.shift) { - command = Some(Command::MultiSelectRange(idx)) - } else if ui.input(|i| i.modifiers.ctrl) { - command = Some(Command::MultiSelectSwitch(idx)) - } else { - command = Some(Command::MultiSelect(idx)) - } - } else { - command = Some(Command::Select(info.clone())); - } - } - - if response.double_clicked() { - match self.dialog_type { - DialogType::SelectFolder => { - // Always open folder on double click, otherwise SelectFolder cant enter sub-folders. - command = Some(Command::OpenSelected); - } - // Open or save file only if name matches filter. - DialogType::OpenFile => { - if info.is_dir() { - command = - Some(Command::BrowseDirectory(info.clone())); - } else if (self.filename_filter)( - self.filename_edit.as_str(), - ) { - command = Some(Command::Open(info.clone())); - } - } - DialogType::SaveFile => { - if info.is_dir() { - command = Some(Command::OpenSelected); - } else if (self.filename_filter)( - self.filename_edit.as_str(), - ) { - command = Some(Command::Save(info.clone())); - } - } - } - } - } - }) - .response - } - Err(e) => ui.label(e.to_string()), - }, - ); - }); - - if let Some(command) = command { - match command { - Command::Select(info) => self.select(Some(info)), - Command::MultiSelect(idx) => self.select_reset_multi(idx), - Command::MultiSelectRange(idx) => self.select_range(idx), - Command::MultiSelectSwitch(idx) => self.select_switch_multi(idx), - Command::Folder => { - let path = self.get_folder().to_owned(); - self.selected_file = Some(FileInfo::new(path)); - self.confirm(); - } - Command::Open(path) => { - self.select(Some(path)); - self.open_selected(); - } - Command::OpenSelected => self.open_selected(), - Command::BrowseDirectory(dir) => { - self.selected_file = Some(dir); - self.open_selected(); - } - Command::Save(file) => { - self.selected_file = Some(file); - self.confirm(); - } - Command::Cancel => self.state = State::Cancelled, - Command::Refresh => self.refresh(), - Command::UpDirectory => { - if self.path.pop() { - self.refresh(); - } - } - Command::CreateDirectory => { - let mut path = self.path.clone(); - let name = match self.filename_edit.is_empty() { - true => self.new_folder_name_text, - false => &self.filename_edit, - }; - path.push(name); - match fs::create_dir(&path) { - Ok(_) => { - self.refresh(); - self.select(Some(FileInfo::new(path))); - // TODO: scroll to selected? - } - Err(err) => println!("Error while creating directory: {err}"), - } - } - Command::Rename(from, to) => match fs::rename(from, &to) { - Ok(_) => { - self.refresh(); - self.select(Some(FileInfo::new(to))); - } - Err(err) => println!("Error while renaming: {err}"), - }, - }; - } - } - - pub fn ui_embeded(&mut self, ui: &mut Ui) { - enum Command { - Open(FileInfo), - OpenSelected, - BrowseDirectory(FileInfo), - Refresh, - Save(FileInfo), - Select(FileInfo), - MultiSelectRange(usize), - MultiSelect(usize), - MultiSelectSwitch(usize), - UpDirectory, - } - let mut command: Option = None; - - // Top directory field with buttons. - egui::TopBottomPanel::top("egui_file_top_new").show_inside(ui, |ui| { - ui.horizontal(|ui| { - ui.add_enabled_ui(self.path.parent().is_some(), |ui| { - let response = ui - .button("⬆") - .on_hover_text(self.parent_folder_button_hover_text); - if response.clicked() { - command = Some(Command::UpDirectory); - } - }); - ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| { - let response = ui.button("⟲").on_hover_text(self.refresh_button_hover_text); - if response.clicked() { - command = Some(Command::Refresh); - } - - let response = ui.add_sized( - ui.available_size(), - TextEdit::singleline(&mut self.path_edit), - ); - - if response.lost_focus() { - let path = PathBuf::from(&self.path_edit); - command = Some(Command::Open(FileInfo::new(path))); - } - }); - }); - ui.add_space(ui.spacing().item_spacing.y); - }); - - // File list. - egui::CentralPanel::default().show_inside(ui, |ui| { - ScrollArea::vertical().show_rows( - ui, - ui.text_style_height(&egui::TextStyle::Body), - self.files.as_ref().map_or(0, |files| files.len()), - |ui, range| match self.files.as_ref() { - Ok(files) => { - ui.with_layout(ui.layout().with_cross_justify(true), |ui| { - let selected = self.selected_file.as_ref().map(|info| &info.path); - let range_start = range.start; - - for (n, info) in files[range].iter().enumerate() { - let idx = n + range_start; - let label = match info.is_dir() { - true => "🗀 ", - false => "🗋 ", - } - .to_string() - + get_file_name(info); - - let is_selected = if self.multi_select_enabled { - files[idx].selected - } else { - Some(&info.path) == selected - }; - let response = ui.selectable_label(is_selected, label); - if response.clicked() { - if self.multi_select_enabled { - if ui.input(|i| i.modifiers.shift) { - command = Some(Command::MultiSelectRange(idx)) - } else if ui.input(|i| i.modifiers.ctrl) { - command = Some(Command::MultiSelectSwitch(idx)) - } else { - command = Some(Command::MultiSelect(idx)) - } - } else { - command = Some(Command::Select(info.clone())); - } - } - - if response.double_clicked() { - match self.dialog_type { - DialogType::SelectFolder => { - // Always open folder on double click, otherwise SelectFolder cant enter sub-folders. - command = Some(Command::OpenSelected); - } - // Open or save file only if name matches filter. - DialogType::OpenFile => { - if info.is_dir() { - command = - Some(Command::BrowseDirectory(info.clone())); - } else if (self.filename_filter)( - self.filename_edit.as_str(), - ) { - // command = Some(Command::Open(info.clone())); // AC: Commented out to prevent opening files on double click - } - } - DialogType::SaveFile => { - if info.is_dir() { - command = Some(Command::OpenSelected); - } else if (self.filename_filter)( - self.filename_edit.as_str(), - ) { - command = Some(Command::Save(info.clone())); - } - } - } - } - } - }) - .response - } - Err(e) => ui.label(e.to_string()), - }, - ); - }); - - if let Some(command) = command { - match command { - Command::Select(info) => self.select(Some(info)), - Command::MultiSelect(idx) => self.select_reset_multi(idx), - Command::MultiSelectRange(idx) => self.select_range(idx), - Command::MultiSelectSwitch(idx) => self.select_switch_multi(idx), - Command::Open(path) => { - self.select(Some(path)); - self.open_selected(); - } - Command::OpenSelected => self.open_selected(), - Command::BrowseDirectory(dir) => { - self.selected_file = Some(dir); - self.open_selected(); - } - Command::Save(file) => { - self.selected_file = Some(file); - self.confirm(); - } - Command::Refresh => self.refresh(), - Command::UpDirectory => { - if self.path.pop() { - self.refresh(); - } - } - }; - } - } - - pub fn selected_file_paths(&self) -> Vec { - let mut paths = Vec::new(); - if let Ok(files) = &self.files { - for file in files { - if file.selected { - paths.push(file.path.clone()); - } - } - } - paths - } - - pub fn set_filter(&mut self, suffix: &str) { - let suffix_owned = suffix.to_string(); - self.filename_filter = Box::new(move |file_name: &str| file_name.ends_with(&suffix_owned)); - self.refresh(); - } - - fn get_folder(&self) -> &Path { - if let Some(info) = &self.selected_file { - if info.is_dir() { - return info.path.as_path(); - } - } - - // No selected file or it's not a folder, so use the current path. - &self.path - } - - fn read_folder(&self) -> Result, Error> { - fs::read_dir(&self.path).map(|entries| { - let mut file_infos: Vec = entries - .filter_map(|result| result.ok()) - .filter_map(|entry| { - let info = FileInfo::new(entry.path()); - if !info.is_dir() { - if !self.show_system_files && !info.path.is_file() { - // Do not show system files. - return None; - } - - // Filter. - if !(self.show_files_filter)(&info.path) { - return None; - } - } - - #[cfg(unix)] - if !self.show_hidden && get_file_name(&info).starts_with('.') { - return None; - } - - Some(info) - }) - .collect(); - - // Sort with folders before files. - file_infos.sort_by(|a, b| match b.is_dir().cmp(&a.is_dir()) { - Ordering::Less => Ordering::Less, - Ordering::Equal => a.path.file_name().cmp(&b.path.file_name()), - Ordering::Greater => Ordering::Greater, - }); - - #[cfg(windows)] - let file_infos = match self.show_drives { - true => { - let drives = get_drives(); - let mut infos = Vec::with_capacity(drives.len() + file_infos.len()); - for drive in drives { - infos.push(FileInfo::new(drive)); - } - infos.append(&mut file_infos); - infos - } - false => file_infos, - }; - - file_infos - }) - } -} - -#[derive(Clone, Debug, Default)] -struct FileInfo { - path: PathBuf, - file_type: Option, - selected: bool, -} - -impl FileInfo { - fn new(path: PathBuf) -> Self { - let file_type = fs::metadata(&path).ok().map(|meta| meta.file_type()); - Self { - path, - file_type, - selected: false, - } - } - - fn is_file(&self) -> bool { - self.file_type.is_some_and(|file_type| file_type.is_file()) - } - - fn is_dir(&self) -> bool { - self.file_type.is_some_and(|file_type| file_type.is_dir()) - } -} - -#[cfg(windows)] -fn get_drives() -> Vec { - let mut drive_names = Vec::new(); - let mut drives = unsafe { GetLogicalDrives() }; - let mut letter = b'A'; - while drives > 0 { - if drives & 1 != 0 { - drive_names.push(format!("{}:\\", letter as char).into()); - } - drives >>= 1; - letter += 1; - } - drive_names -} - -#[cfg(windows)] -fn is_drive_root(path: &Path) -> bool { - path.to_str() - .filter(|path| &path[1..] == ":\\") - .and_then(|path| path.chars().next()) - .map_or(false, |ch| ch.is_ascii_uppercase()) -} - -fn get_file_name(info: &FileInfo) -> &str { - #[cfg(windows)] - if info.is_dir() && is_drive_root(&info.path) { - return info.path.to_str().unwrap_or_default(); - } - info.path - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default() -} - -#[cfg(windows)] -extern "C" { - pub fn GetLogicalDrives() -> u32; -} diff --git a/src/util/mod.rs b/src/util/mod.rs index 7f66ac3..7d1aafc 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,2 +1 @@ -pub mod egui_file_dialog; pub mod processer; diff --git a/src/util/processer.rs b/src/util/processer.rs index a83d8d1..0c0e8da 100644 --- a/src/util/processer.rs +++ b/src/util/processer.rs @@ -2,24 +2,26 @@ use crate::histoer::histogrammer::Histogrammer; use crate::histogram_scripter::histogram_script::HistogramScript; use pyo3::{prelude::*, types::PyModule}; -use super::egui_file_dialog::FileDialog; +use egui_file_dialog::FileDialog; use polars::prelude::*; use std::sync::atomic::Ordering; #[derive(serde::Deserialize, serde::Serialize)] pub struct ProcessorSettings { - pub dialog_open: bool, + pub left_panel_open: bool, pub histogram_script_open: bool, pub column_names: Vec, + pub estimated_memory: f64, } impl Default for ProcessorSettings { fn default() -> Self { Self { - dialog_open: true, + left_panel_open: true, histogram_script_open: true, column_names: Vec::new(), + estimated_memory: 4.0, } } } @@ -27,7 +29,7 @@ impl Default for ProcessorSettings { #[derive(Default, serde::Deserialize, serde::Serialize)] pub struct Processor { #[serde(skip)] - pub file_dialog: Option, + pub file_dialog: FileDialog, pub selected_files: Vec, #[serde(skip)] pub lazyframe: Option, @@ -39,7 +41,7 @@ pub struct Processor { impl Processor { pub fn new() -> Self { Self { - file_dialog: None, + file_dialog: FileDialog::new(), selected_files: Vec::new(), lazyframe: None, histogrammer: Histogrammer::default(), @@ -301,8 +303,11 @@ def get_2d_histograms(file_name): fn perform_histogrammer_from_lazyframe(&mut self) { if let Some(lf) = &self.lazyframe { - self.histogram_script - .add_histograms(&mut self.histogrammer, lf.clone()); + self.histogram_script.add_histograms( + &mut self.histogrammer, + lf.clone(), + self.settings.estimated_memory, + ); } else { log::error!("Failed to preform histogrammer: LazyFrame is None."); } @@ -338,38 +343,64 @@ def get_2d_histograms(file_name): } } - fn open_file_dialog(&mut self) { - self.file_dialog = Some(FileDialog::open_file(None).multi_select(true)); - if let Some(dialog) = &mut self.file_dialog { - dialog.set_filter(".parquet"); - dialog.open(); // Modify the dialog in-place to open it - } - } - pub fn left_side_panels_ui(&mut self, ctx: &egui::Context) { egui::SidePanel::left("spectrix_processor_left_panel").show_animated( ctx, - self.settings.dialog_open, + self.settings.left_panel_open, |ui| { ui.horizontal(|ui| { - if ui - .add_enabled( - !self.selected_files.is_empty(), - egui::Button::new("Calculate/Get Histograms"), - ) - .on_disabled_hover_text("No files selected.") - .clicked() - { - self.calculate_histograms(); + if ui.button("Get Files").clicked() { + self.file_dialog.pick_multiple(); } - if self.histogrammer.calculating.load(Ordering::Relaxed) { - // Show spinner while `calculating` is true - ui.add(egui::widgets::Spinner::default()); + if let Some(files) = self.file_dialog.take_picked_multiple() { + self.selected_files.extend(files.clone()); + + if let Some(path) = files.first() { + self.file_dialog = + FileDialog::new().initial_directory(path.to_path_buf()); + } } ui.separator(); + ui.vertical(|ui| { + + ui.label("Processor"); + + if ui + .add_enabled( + !self.selected_files.is_empty(), + egui::Button::new("Calculate/Get Histograms"), + ) + .on_disabled_hover_text("No files selected.") + .clicked() + { + self.calculate_histograms(); + } + + ui.add( + egui::DragValue::new(&mut self.settings.estimated_memory) + .range(0.1..=f64::INFINITY) + .speed(1) + .prefix("Estimated Memory: ") + .suffix(" GB"), + ).on_hover_text("Estimated memory in GB. This is an approximation based off the rows and columns in a lazyframe, so set it lower that the actual memory to avoid crashes."); + + if self.histogrammer.calculating.load(Ordering::Relaxed) { + // Show spinner while `calculating` is true + ui.horizontal(|ui| { + ui.label("Calculating"); + ui.add(egui::widgets::Spinner::default()); + ui.separator(); + if ui.button("Cancel").clicked() { + self.histogrammer.abort_flag.store(true, Ordering::Relaxed); + } + }); + } + }); + ui.separator(); + if ui .selectable_label(self.settings.histogram_script_open, "Histograms") .clicked() @@ -380,20 +411,19 @@ def get_2d_histograms(file_name): ui.separator(); - if self.file_dialog.is_none() { - self.open_file_dialog(); + ui.label("Selected files:"); + if ui.button("Clear").clicked() { + self.selected_files.clear(); } - - if let Some(dialog) = &mut self.file_dialog { - dialog.ui_embeded(ui); - self.selected_files = dialog.selected_file_paths(); + for file in self.selected_files.iter() { + ui.label(file.to_str().unwrap()); } }, ); egui::SidePanel::left("spectrix_histogram_panel").show_animated( ctx, - self.settings.histogram_script_open && self.settings.dialog_open, + self.settings.histogram_script_open && self.settings.left_panel_open, |ui| { self.histogram_script.ui(ui); }, @@ -408,14 +438,14 @@ def get_2d_histograms(file_name): ui.vertical(|ui| { ui.add_space(ui.available_height() / 2.0 - 10.0); // Center the button vertically if ui - .small_button(if self.settings.dialog_open { + .small_button(if self.settings.left_panel_open { "◀" } else { "▶" }) .clicked() { - self.settings.dialog_open = !self.settings.dialog_open; + self.settings.left_panel_open = !self.settings.left_panel_open; } }); }); @@ -430,5 +460,7 @@ def get_2d_histograms(file_name): pub fn ui(&mut self, ctx: &egui::Context) { self.left_side_panels_ui(ctx); self.central_panel_ui(ctx); + + self.file_dialog.update(ctx); } }