Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ preparation.
## Highlights

- **Bring scientific data together.** Current import support includes Axon
ABF2 patch-clamp recordings, mzML and Waters MassLynx LC–MS runs, JEOL Delta
and Bruker TopSpin experiments, JCAMP-DX spectra, archives, and delimited
tables.
ABF2 patch-clamp recordings, Rigaku powder XRD patterns, mzML and Waters
MassLynx LC–MS runs, JEOL Delta and Bruker TopSpin experiments, JCAMP-DX
spectra, archives, and delimited tables.
- **Process and analyze interactively.** Build ordered processing pipelines,
then pick peaks, integrate regions, and fit data. NMR workflows also include
DOSY and relaxation analysis, plus sweep statistics and IV analysis for
Expand Down
3 changes: 2 additions & 1 deletion crates/app/src/ui/file_dialogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,10 @@ pub(crate) fn load_and_note(app: &mut PlotxApp, path: &std::path::Path) {
pub(crate) fn open_file(app: &mut PlotxApp) {
if let Some(paths) = rfd::FileDialog::new()
.add_filter(
"All supported data (*.mzML, *.spm, *.pfc, *.abf, *.jdf, fid, ser, *.zip, *.opj)",
"All supported data (*.mzML, *.rasx, *.raw, *.spm, *.pfc, *.abf, *.jdf, fid, ser, *.zip, *.opj)",
origin::OPEN_FILE_FILTER_EXTENSIONS,
)
.add_filter("Rigaku XRD (*.rasx, *.raw, *.txt)", &["rasx", "raw", "txt"])
.add_filter(
origin::ORIGIN_PROJECT_FILTER_LABEL,
origin::ORIGIN_PROJECT_FILTER_EXTENSIONS,
Expand Down
26 changes: 23 additions & 3 deletions crates/app/src/ui/file_dialogs/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ pub(super) fn collect_data_files(folder: &Path, output: &mut Vec<PathBuf>) {
.extension()
.and_then(|value| value.to_str())
.unwrap_or("");
if ["abf", "spm", "pfc"]
let supported_extension = ["abf", "spm", "pfc", "rasx"]
.iter()
.any(|supported| extension.eq_ignore_ascii_case(supported))
{
.any(|supported| extension.eq_ignore_ascii_case(supported));
let recognized_raw =
extension.eq_ignore_ascii_case("raw") && plotx_io::xrd::is_rigaku_raw(&path);
if supported_extension || recognized_raw {
output.push(path);
}
}
Expand All @@ -50,4 +52,22 @@ mod tests {
assert_eq!(found.as_slice(), std::slice::from_ref(&root));
std::fs::remove_dir_all(root).unwrap();
}

#[test]
fn folder_scan_keeps_only_recognized_raw_files() {
let root =
std::env::temp_dir().join(format!("plotx-xrd-discovery-{}", uuid::Uuid::new_v4()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir(&root).unwrap();
let xrd = root.join("pattern.raw");
let unrelated = root.join("unrelated.raw");
std::fs::write(&xrd, b"FI\0\0").unwrap();
std::fs::write(&unrelated, b"not an XRD file").unwrap();

let mut found = Vec::new();
collect_data_files(&root, &mut found);

assert_eq!(found, vec![xrd]);
std::fs::remove_dir_all(root).unwrap();
}
}
2 changes: 1 addition & 1 deletion crates/app/src/ui/file_dialogs/origin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub(super) const ORIGIN_PROJECT_FILTER_LABEL: &str =
"Origin projects (experimental: OPJ import; OPJU recognition only)";
pub(super) const ORIGIN_PROJECT_FILTER_EXTENSIONS: &[&str] = &["opj", "opju"];
pub(super) const OPEN_FILE_FILTER_EXTENSIONS: &[&str] = &[
"mzML", "spm", "pfc", "abf", "jdf", "fid", "ser", "zip", "opj",
"mzML", "rasx", "raw", "spm", "pfc", "abf", "jdf", "fid", "ser", "zip", "opj",
];

const ORIGIN_MEDIA_TYPE: &str = "application/x-origin-project";
Expand Down
2 changes: 2 additions & 0 deletions crates/app/src/ui/file_dialogs/recent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ fn extension_open_kind(path: &Path) -> RecentOpenKind {
};
if has_extension("plotx") {
RecentOpenKind::Project
} else if has_extension("txt") && plotx_io::xrd::is_rigaku_profile(path) {
RecentOpenKind::DataFile
} else if has_extension("csv") || has_extension("tsv") || has_extension("txt") {
RecentOpenKind::DelimitedTable
} else if has_extension("xlsx") {
Expand Down
106 changes: 52 additions & 54 deletions crates/app/src/ui/tools/curve_fit.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use egui::{Area, Button, Order, Ui};
use egui::{Button, Ui};
use egui_phosphor::regular as icon;
use plotx_core::state::{Dataset, PlotxApp, TableDataset, TaskDockTab};

Expand Down Expand Up @@ -60,65 +60,63 @@ pub(crate) fn render_task(app: &mut PlotxApp, host: &mut Ui) {
let mut close = false;
let mut toggle_collapse = false;

Area::new(egui::Id::new("curve_fit_task_card"))
.order(Order::Foreground)
.fixed_pos(pos)
.show(host.ctx(), |ui| {
ui.set_width(width);
crate::ui::card_frame(dark, egui::Margin::ZERO).show(ui, |ui| {
if task_card::tab_bar(app, TaskDockTab::CurveFit, ui) {
ui.separator();
}
let table = app.doc.datasets[di].as_table().unwrap();
let curves = table.series_bindings.len();
let points = table.typed_state.envelope.revision.snapshot.row_count;
ui.horizontal(|ui| {
crate::typography::headline_label(ui, "Curve Fit");
let curve_count = if curves == 1 {
"1 curve".to_owned()
let area_id = egui::Id::new("curve_fit_task_card");
task_card::area(host, area_id, pos).show(host.ctx(), |ui| {
ui.set_width(width);
crate::ui::card_frame(dark, egui::Margin::ZERO).show(ui, |ui| {
if task_card::tab_bar(app, TaskDockTab::CurveFit, ui) {
ui.separator();
}
let table = app.doc.datasets[di].as_table().unwrap();
let curves = table.series_bindings.len();
let points = table.typed_state.envelope.revision.snapshot.row_count;
task_card::header(ui, area_id, |ui| {
crate::typography::headline_label(ui, "Curve Fit");
let curve_count = if curves == 1 {
"1 curve".to_owned()
} else {
format!("{curves} curves")
};
ui.weak(format!("{curve_count} · {points} points each"));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui
.small_button(icon::X)
.on_hover_text("Close Curve Fit")
.clicked()
{
close = true;
}
let glyph = if collapsed {
icon::CARET_DOWN
} else {
format!("{curves} curves")
icon::CARET_UP
};
ui.weak(format!("{curve_count} · {points} points each"));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui
.small_button(icon::X)
.on_hover_text("Close Curve Fit")
.clicked()
{
close = true;
}
let glyph = if collapsed {
icon::CARET_DOWN
if ui
.small_button(glyph)
.on_hover_text(if collapsed {
"Expand Curve Fit"
} else {
icon::CARET_UP
};
if ui
.small_button(glyph)
.on_hover_text(if collapsed {
"Expand Curve Fit"
} else {
"Collapse Curve Fit"
})
.clicked()
{
toggle_collapse = true;
}
});
"Collapse Curve Fit"
})
.clicked()
{
toggle_collapse = true;
}
});
if !collapsed {
ui.separator();
egui::Resize::default()
.id_salt("curve_fit_task_body_resize")
.default_size([ui.available_width(), default_body_height])
.min_size([ui.available_width(), min_body_height])
.max_size([ui.available_width(), max_body_height])
.resizable([false, true])
.with_stroke(false)
.show(ui, |ui| curve_fit_task_body(app, di, ui));
}
});
if !collapsed {
ui.separator();
task_card::resizable_body(
ui,
"curve_fit_task_body_resize",
default_body_height,
min_body_height,
max_body_height,
|ui| curve_fit_task_body(app, di, ui),
);
}
});
});

if toggle_collapse {
app.session.ui.curve_fit_task_collapsed = !collapsed;
Expand Down
7 changes: 5 additions & 2 deletions crates/app/src/ui/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,11 @@ pub(crate) fn expand_processing_surface(app: &mut PlotxApp) {
else {
return;
};
if !matches!(dataset, Dataset::Nmr(_) | Dataset::Nmr2D(_)) {
app.session.status = "Select an NMR dataset before opening Processing.".to_owned();
if !matches!(
dataset,
Dataset::Nmr(_) | Dataset::Nmr2D(_) | Dataset::Xrd(_)
) {
app.session.status = "Select a processable dataset before opening Processing.".to_owned();
return;
}
app.session.ui.processing_task_dataset = Some(dataset.resource_id());
Expand Down
8 changes: 6 additions & 2 deletions crates/app/src/ui/tools/processing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use egui_phosphor::regular as icon;
use plotx_core::actions::DatasetProcessingState;
use plotx_core::automation::{ResourceRef, TargetRef};
use plotx_core::state::{Dataset, DatasetId, PhaseAxis, PlotxApp};
use plotx_processing::xrd::XrdProcessing;
use plotx_processing::{
Apodization, AxisPipeline, BaselineMethod, BinParams, NormalizeMethod, PhaseParams,
ProcessingStep, ReferenceParams, SmoothMethod, StepId, StepKind, StepSource, ZeroFill,
Expand Down Expand Up @@ -176,7 +177,8 @@ fn add_step_menu(app: &mut PlotxApp, di: usize, axis: PhaseAxis, ui: &mut Ui) {
Dataset::Table(_)
| Dataset::Electrophysiology(_)
| Dataset::Afm(_)
| Dataset::MassSpec(_) => return,
| Dataset::MassSpec(_)
| Dataset::Xrd(_) => return,
};
let Some(pipeline) = dataset.axis_pipeline(axis) else {
return;
Expand Down Expand Up @@ -325,7 +327,8 @@ fn apply_row_op(app: &mut PlotxApp, di: usize, axis: PhaseAxis, id: StepId, op:
Dataset::Table(_)
| Dataset::Electrophysiology(_)
| Dataset::Afm(_)
| Dataset::MassSpec(_) => return,
| Dataset::MassSpec(_)
| Dataset::Xrd(_) => return,
};
let before = DatasetProcessingState::from_dataset(dataset);
let mut after = before.clone();
Expand Down Expand Up @@ -472,6 +475,7 @@ fn is_default_processing(dataset: &Dataset) -> bool {
..
},
) => ga == gb && a.layout == b.layout && pipe_eq(&a.f2, &b.f2) && pipe_eq(&a.f1, &b.f1),
(DatasetProcessingState::Xrd(a), DatasetProcessingState::Xrd(b)) => a == b,
_ => false,
}
}
Expand Down
Loading
Loading