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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ preparation.

- **Bring scientific data together.** Current import support includes Axon
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.
MassLynx LC–MS runs, JEOL Delta, Bruker TopSpin, and Varian/Agilent VnmrJ
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 @@ -393,6 +393,7 @@ pub(crate) fn open_file(app: &mut PlotxApp) {
.add_filter("mzML mass spectrometry (*.mzML)", &["mzML"])
.add_filter("XPS (*.vms, CasaXPS *.txt)", &["vms", "txt"])
.add_filter("Bruker TopSpin (fid, ser)", &["fid", "ser"])
.add_filter("Varian/Agilent VnmrJ (fid)", &["fid"])
.add_filter("Archive (*.zip)", &["zip"])
.add_filter("All files", &["*"])
.set_title("Open data or add images — format is detected automatically")
Expand Down Expand Up @@ -426,7 +427,7 @@ pub(crate) fn choose_project_save_path() -> Option<std::path::PathBuf> {

pub(crate) fn open_folder(app: &mut PlotxApp) {
if let Some(path) = rfd::FileDialog::new()
.set_title("Open a data folder (Waters MassLynx RAW, Bruker, or recursive AFM/ABF2 import)")
.set_title("Open a data folder (Waters MassLynx RAW, Bruker, Varian/Agilent VnmrJ, or recursive AFM/ABF2 import)")
.pick_folder()
{
open_folder_path(app, &path);
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
@@ -1,9 +1,13 @@
use std::path::{Path, PathBuf};

pub(super) fn collect_data_files(folder: &Path, output: &mut Vec<PathBuf>) {
// A MassLynx `.raw` directory is one atomic acquisition. Its numbered
// payload files must never be rediscovered as independent datasets.
if plotx_io::waters::is_masslynx_raw(folder) {
// Vendor acquisition directories are atomic. Their payload files must
// never be rediscovered as independent datasets.
if plotx_io::waters::is_masslynx_raw(folder)
|| plotx_io::bruker::detect_processed(folder).is_some()
|| plotx_io::bruker::is_bruker_dir(folder)
|| plotx_io::varian::is_varian(folder)
{
output.push(folder.to_owned());
return;
}
Expand Down Expand Up @@ -72,4 +76,20 @@ mod tests {
assert_eq!(found, vec![xrd]);
std::fs::remove_dir_all(root).unwrap();
}

#[test]
fn varian_directory_is_atomic() {
let root =
std::env::temp_dir().join(format!("plotx-varian-discovery-{}", uuid::Uuid::new_v4()));
let dataset = root.join("sample.fid");
std::fs::create_dir_all(&dataset).unwrap();
std::fs::write(dataset.join("procpar"), b"sw 1 1\n1 1000\n0\n").unwrap();
std::fs::write(dataset.join("fid"), [0; 32]).unwrap();

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

assert_eq!(found, vec![dataset]);
std::fs::remove_dir_all(root).unwrap();
}
}
4 changes: 2 additions & 2 deletions crates/core/src/state/app_impl_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@ impl PlotxApp {
}
}

/// Open a `.zip` archive as a batch: extract it and load every JEOL `.jdf`
/// file and Bruker acquisition folder inside, each as its own dataset and
/// Open a `.zip` archive as a batch: extract it and load every supported
/// loose file and atomic acquisition folder inside, each as its own dataset and
/// canvas.
pub fn load_archive_from(&mut self, path: &std::path::Path) {
let archive = Self::short_name(&path.to_string_lossy());
Expand Down
9 changes: 6 additions & 3 deletions crates/io/src/archive.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Batch loading from a `.zip` archive: extract to a scratch directory, then
//! walk the tree loading every supported loose spectrum and Bruker acquisition
//! walk the tree loading every supported loose spectrum and acquisition
//! folder.

use crate::{IoError, LoadResult, LoadWarning, LoadWarningCode};
Expand Down Expand Up @@ -72,11 +72,14 @@ fn scratch_dir() -> PathBuf {
))
}

// Depth-first walk appending each loadable dataset. A Bruker acquisition folder
// Depth-first walk appending each loadable dataset. An acquisition folder
// is loaded as a unit and not descended into; any other directory is recursed;
// loose JEOL and JCAMP-DX files are read individually.
fn collect_acquisitions(dir: &Path, out: &mut ArchiveLoadResult) {
if crate::bruker::detect_processed(dir).is_some() || crate::bruker::is_bruker_dir(dir) {
if crate::bruker::detect_processed(dir).is_some()
|| crate::bruker::is_bruker_dir(dir)
|| crate::varian::is_varian(dir)
{
match crate::load_path(dir) {
Ok(result) => out.items.push(result),
Err(error) => out.warnings.push(entry_warning(dir, error)),
Expand Down
15 changes: 14 additions & 1 deletion crates/io/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod mass_spec;
pub mod mzml;
pub mod nanoscope;
pub mod origin;
pub mod varian;
pub mod waters;
pub mod xlsx;
pub mod xps;
Expand All @@ -27,6 +28,7 @@ pub enum DataFormat {
Abf2,
JeolDelta,
BrukerRaw,
VarianAgilentRaw,
BrukerProcessed1D,
BrukerProcessed2D,
JcampDx1D,
Expand All @@ -47,6 +49,7 @@ impl DataFormat {
Self::Abf2 => "abf2",
Self::JeolDelta => "jeol-delta",
Self::BrukerRaw => "bruker-raw",
Self::VarianAgilentRaw => "varian-agilent-raw",
Self::BrukerProcessed1D => "bruker-processed-1d",
Self::BrukerProcessed2D => "bruker-processed-2d",
Self::JcampDx1D => "jcamp-dx-1d",
Expand Down Expand Up @@ -615,6 +618,12 @@ pub enum IoError {

#[error("invalid XPS data: {0}")]
InvalidXps(String),

#[error("invalid Varian/Agilent VnmrJ data: {0}")]
InvalidVarian(String),

#[error("unsupported Varian/Agilent VnmrJ data: {0}")]
UnsupportedVarian(String),
}

/// Load a dataset, auto-detecting the format from the path. A Bruker
Expand All @@ -637,6 +646,9 @@ pub fn detect_format(path: impl AsRef<Path>) -> Result<DataFormat, IoError> {
if bruker::is_bruker(path) {
return Ok(DataFormat::BrukerRaw);
}
if varian::is_varian(path) {
return Ok(DataFormat::VarianAgilentRaw);
}
let ext = path
.extension()
.and_then(|e| e.to_str())
Expand All @@ -661,7 +673,7 @@ pub fn detect_format(path: impl AsRef<Path>) -> Result<DataFormat, IoError> {
_ if abf2::is_abf2(path) => Ok(DataFormat::Abf2),
_ if jeol::is_jdf(path) => Ok(DataFormat::JeolDelta),
_ => Err(IoError::Unsupported(format!(
"unrecognised path {}: expected mzML, Rigaku FI .raw/.rasx/profile .txt, a Waters .raw directory, NanoScope .spm/.pfc, ABF2 .abf, JEOL .jdf, JCAMP-DX .dx/.jdx/.jcamp, Bruker fid/ser, or Bruker pdata",
"unrecognised path {}: expected mzML, Rigaku FI .raw/.rasx/profile .txt, a Waters .raw directory, NanoScope .spm/.pfc, ABF2 .abf, JEOL .jdf, JCAMP-DX .dx/.jdx/.jcamp, Bruker fid/ser or pdata, or a Varian/Agilent VnmrJ .fid directory",
path.display()
))),
}
Expand All @@ -683,6 +695,7 @@ pub fn load_path(path: impl AsRef<Path>) -> Result<LoadResult, IoError> {
warnings: Vec::new(),
}),
DataFormat::BrukerRaw => bruker::load_raw(path),
DataFormat::VarianAgilentRaw => varian::load_raw(path),
DataFormat::BrukerProcessed1D | DataFormat::BrukerProcessed2D => {
bruker::load_processed(path)
}
Expand Down
Loading
Loading