diff --git a/Cargo.lock b/Cargo.lock index 6a4f23f..e04c224 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -712,7 +712,7 @@ checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "filemanager" -version = "0.1.3" +version = "0.1.4" dependencies = [ "arrow", "futures", @@ -2431,7 +2431,7 @@ dependencies = [ [[package]] name = "timsrust" -version = "0.5.3" +version = "0.5.4" dependencies = [ "rayon", "thiserror", @@ -2443,11 +2443,12 @@ dependencies = [ "timsrust-patched", "timsrust-sdk", "timsrust-tdf", + "timsrust-tsf", ] [[package]] name = "timsrust-centroid" -version = "0.1.3" +version = "0.1.4" dependencies = [ "rayon", "rustc-hash", @@ -2457,7 +2458,7 @@ dependencies = [ [[package]] name = "timsrust-centroid-cli" -version = "0.1.3" +version = "0.1.4" dependencies = [ "clap", "env_logger", @@ -2474,7 +2475,7 @@ dependencies = [ [[package]] name = "timsrust-cli-core" -version = "0.1.3" +version = "0.1.4" dependencies = [ "clap", "env_logger", @@ -2484,7 +2485,7 @@ dependencies = [ [[package]] name = "timsrust-core" -version = "0.1.3" +version = "0.1.4" dependencies = [ "filemanager", "rayon", @@ -2493,14 +2494,14 @@ dependencies = [ [[package]] name = "timsrust-mgf" -version = "0.1.3" +version = "0.1.4" dependencies = [ "timsrust-core", ] [[package]] name = "timsrust-mgf-cli" -version = "0.1.3" +version = "0.1.4" dependencies = [ "timsrust", "timsrust-cli-core", @@ -2509,7 +2510,7 @@ dependencies = [ [[package]] name = "timsrust-minitdf" -version = "0.1.3" +version = "0.1.4" dependencies = [ "bytemuck", "serde", @@ -2520,7 +2521,7 @@ dependencies = [ [[package]] name = "timsrust-parquet-spectra" -version = "0.1.3" +version = "0.1.4" dependencies = [ "serde", "timsrust-core", @@ -2528,7 +2529,7 @@ dependencies = [ [[package]] name = "timsrust-parquet-spectra-cli" -version = "0.1.3" +version = "0.1.4" dependencies = [ "clap", "env_logger", @@ -2547,7 +2548,7 @@ checksum = "a549f6a10c5b02f1c890e5cd622d36dab36bc8bd8bf076abbc6e1990a046e21d" [[package]] name = "timsrust-pyo3" -version = "0.1.3" +version = "0.1.4" dependencies = [ "pyo3", "rayon", @@ -2556,7 +2557,7 @@ dependencies = [ [[package]] name = "timsrust-sdk" -version = "0.1.3" +version = "0.1.4" dependencies = [ "libc", "serde", @@ -2565,7 +2566,7 @@ dependencies = [ [[package]] name = "timsrust-tdf" -version = "0.1.3" +version = "0.1.4" dependencies = [ "lzf", "rayon", @@ -2577,7 +2578,7 @@ dependencies = [ [[package]] name = "timsrust-tsf" -version = "0.1.3" +version = "0.1.4" dependencies = [ "serde", "thiserror", @@ -2587,7 +2588,7 @@ dependencies = [ [[package]] name = "timsrust-utils" -version = "0.1.3" +version = "0.1.4" dependencies = [ "rayon", ] diff --git a/Cargo.toml b/Cargo.toml index 0412bc9..b6deb2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,20 +13,20 @@ unreachable_pub = "warn" [workspace.dependencies] # crates -filemanager = { path = "crates/filemanager", version = "0.1.3", default-features = false } -timsrust = { path = "crates/timsrust", version = "0.5.3", default-features = false } -timsrust-centroid = { path = "crates/timsrust-centroid", version = "0.1.3", default-features = false } -timsrust-core = { path = "crates/timsrust-core", version = "0.1.3", default-features = false } -timsrust-mgf = { path = "crates/timsrust-mgf", version = "0.1.3", default-features = false } -timsrust-minitdf = { path = "crates/timsrust-minitdf", version = "0.1.3", default-features = false } -timsrust-parquet-spectra = { path = "crates/timsrust-parquet-spectra", version = "0.1.3", default-features = false } -timsrust-sdk = { path = "crates/timsrust-sdk", version = "0.1.3", default-features = false } -timsrust-tdf = { path = "crates/timsrust-tdf", version = "0.1.3", default-features = false } -timsrust-tsf = { path = "crates/timsrust-tsf", version = "0.1.3", default-features = false } -timsrust-utils = { path = "crates/timsrust-utils", version = "0.1.3", default-features = false } +filemanager = { path = "crates/filemanager", version = "0.1.4", default-features = false } +timsrust = { path = "crates/timsrust", version = "0.5.4", default-features = false } +timsrust-centroid = { path = "crates/timsrust-centroid", version = "0.1.4", default-features = false } +timsrust-core = { path = "crates/timsrust-core", version = "0.1.4", default-features = false } +timsrust-mgf = { path = "crates/timsrust-mgf", version = "0.1.4", default-features = false } +timsrust-minitdf = { path = "crates/timsrust-minitdf", version = "0.1.4", default-features = false } +timsrust-parquet-spectra = { path = "crates/timsrust-parquet-spectra", version = "0.1.4", default-features = false } +timsrust-sdk = { path = "crates/timsrust-sdk", version = "0.1.4", default-features = false } +timsrust-tdf = { path = "crates/timsrust-tdf", version = "0.1.4", default-features = false } +timsrust-tsf = { path = "crates/timsrust-tsf", version = "0.1.4", default-features = false } +timsrust-utils = { path = "crates/timsrust-utils", version = "0.1.4", default-features = false } timsrust-patched = { version = "0.1.1", default-features = false } # clis -timsrust-cli-core = { path = "clis/timsrust-cli-core", version = "0.1.3", default-features = false } +timsrust-cli-core = { path = "clis/timsrust-cli-core", version = "0.1.4", default-features = false } # external arrow = { version = "57", default-features = false } bytemuck = { version = "1", default-features = false } diff --git a/clis/timsrust-centroid-cli/Cargo.toml b/clis/timsrust-centroid-cli/Cargo.toml index d84c29a..8bd1ec3 100644 --- a/clis/timsrust-centroid-cli/Cargo.toml +++ b/clis/timsrust-centroid-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-centroid-cli" -version = "0.1.3" +version = "0.1.4" edition = "2024" publish = false diff --git a/clis/timsrust-cli-core/Cargo.toml b/clis/timsrust-cli-core/Cargo.toml index 2480bc4..c181a89 100644 --- a/clis/timsrust-cli-core/Cargo.toml +++ b/clis/timsrust-cli-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-cli-core" -version = "0.1.3" +version = "0.1.4" edition = "2024" publish = false diff --git a/clis/timsrust-mgf-cli/Cargo.toml b/clis/timsrust-mgf-cli/Cargo.toml index 9ac8713..01284e3 100644 --- a/clis/timsrust-mgf-cli/Cargo.toml +++ b/clis/timsrust-mgf-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-mgf-cli" -version = "0.1.3" +version = "0.1.4" edition = "2024" publish = false diff --git a/clis/timsrust-parquet-spectra-cli/Cargo.toml b/clis/timsrust-parquet-spectra-cli/Cargo.toml index 551ea61..01eec6f 100644 --- a/clis/timsrust-parquet-spectra-cli/Cargo.toml +++ b/clis/timsrust-parquet-spectra-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-parquet-spectra-cli" -version = "0.1.3" +version = "0.1.4" edition = "2024" publish = false diff --git a/crates/filemanager/Cargo.toml b/crates/filemanager/Cargo.toml index fa67994..f5c455f 100644 --- a/crates/filemanager/Cargo.toml +++ b/crates/filemanager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "filemanager" -version = "0.1.3" +version = "0.1.4" edition = "2021" description = "Unified file abstraction for local and cloud files with caching options and support for multiple formats." license = "Apache-2.0" diff --git a/crates/filemanager/src/cache.rs b/crates/filemanager/src/cache.rs index 4a88371..08f2238 100644 --- a/crates/filemanager/src/cache.rs +++ b/crates/filemanager/src/cache.rs @@ -132,6 +132,23 @@ impl FileCache { } } + /// Returns the local cache URI for `uri` without checking existence. + /// + /// Like [`cached_local`](Self::cached_local) but never touches the + /// filesystem. Local URIs pass through unchanged. For cloud URIs, + /// returns the path the file *would* live at inside the cache + /// directory (whether or not it has been downloaded yet), or `None` + /// when the URI has no key or no cache directory is configured. + pub fn local_path(&self, uri: impl Into) -> Option { + let uri = uri.into(); + if uri.is_local() { + return Some(uri); + } + let dir = self.dir.as_ref()?; + let key = uri.key()?; + Some(Uri::from(dir.join(key))) + } + pub fn invalidate(&self, uri: impl Into) -> Result<(), CacheError> { match &self.dir { Some(dir) => { @@ -186,4 +203,22 @@ impl Uri { pub fn cached_local(&self) -> Option { crate::global_cache().cached_local(self.clone()) } + + /// Returns the local-filesystem representation of this URI, without + /// performing any network or existence checks. + /// + /// Local URIs are returned unchanged. Cloud URIs are mapped to the + /// path they would occupy inside the configured cache directory + /// (regardless of whether the file has been downloaded). If no cache + /// directory is configured or the URI has no key, the URI is returned + /// unchanged. + /// + /// Useful as a fast pre-check: callers can probe the returned URI + /// with local filesystem operations and only fall back to network + /// access if those probes fail. + pub fn local_representation(&self) -> Uri { + crate::global_cache() + .local_path(self.clone()) + .unwrap_or_else(|| self.clone()) + } } diff --git a/crates/filemanager/src/cloud_store/cloud/authenticate.rs b/crates/filemanager/src/cloud_store/cloud/authenticate.rs index d3524e8..5da5f11 100644 --- a/crates/filemanager/src/cloud_store/cloud/authenticate.rs +++ b/crates/filemanager/src/cloud_store/cloud/authenticate.rs @@ -1,4 +1,5 @@ -use std::sync::Arc; +use std::collections::HashMap; +use std::sync::{Arc, OnceLock, RwLock}; use object_store::{path::Path as ObjectPath, ObjectStore}; use url::Url; @@ -7,6 +8,69 @@ use crate::{ cloud_store::CloudProvider, uri::URI_SCHEME_SEPARATOR, CloudError, }; +/// Process-wide cache of authenticated [`ObjectStore`] instances. +/// +/// Building an `ObjectStore` involves env-var lookups and constructing an +/// HTTP client, both of which are needlessly repeated when many URIs under +/// the same bucket/container are accessed. The cache is keyed by the parts +/// of the URI that determine store identity (scheme, host, and — for +/// virtual-hosted Azure HTTPS URLs — the container path segment). +fn store_cache() -> &'static RwLock>> { + static CACHE: OnceLock>>> = + OnceLock::new(); + CACHE.get_or_init(|| RwLock::new(HashMap::new())) +} + +/// Computes the cache key for a parsed URL + provider. +/// +/// For Azure HTTPS URLs the container is encoded into the bound store, so +/// it must be part of the key; for every other case `scheme://host` is +/// sufficient. +fn store_cache_key(url: &Url, provider: CloudProvider) -> String { + let scheme = url.scheme(); + let host = url.host_str().unwrap_or(""); + if provider == CloudProvider::Azure && scheme == "https" { + let container = + url.path_segments().and_then(|mut s| s.next()).unwrap_or(""); + format!("{scheme}://{host}/{container}") + } else { + format!("{scheme}://{host}") + } +} + +fn build_store( + url: &Url, + host: &str, + provider: CloudProvider, +) -> Result, CloudError> { + match provider { + CloudProvider::S3 => build_s3(url, host), + CloudProvider::Azure => build_azure(url, host), + CloudProvider::Gcs => build_gcs(url, host), + } +} + +fn cached_or_build_store( + url: &Url, + host: &str, + provider: CloudProvider, +) -> Result, CloudError> { + let key = store_cache_key(url, provider); + let cache = store_cache(); + if let Some(store) = cache + .read() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .get(&key) + { + return Ok(store.clone()); + } + let store = build_store(url, host, provider)?; + let mut guard = cache + .write() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + Ok(guard.entry(key).or_insert(store).clone()) +} + // --- credential detection --- fn env_any(vars: &[&str]) -> bool { @@ -169,18 +233,14 @@ impl CloudProvider { let url = Url::parse(raw).map_err(|e| CloudError::ThirdParty(Box::new(e)))?; let host = url.host_str().unwrap_or(""); - let provider = CloudProvider::parse(&url); - let store = match provider { - Some(CloudProvider::S3) => build_s3(&url, host), - Some(CloudProvider::Azure) => build_azure(&url, host), - Some(CloudProvider::Gcs) => build_gcs(&url, host), - None => Err(CloudError::NotACloudUri(url.to_string())), - }?; + let provider = CloudProvider::parse(&url) + .ok_or_else(|| CloudError::NotACloudUri(url.to_string()))?; + let store = cached_or_build_store(&url, host, provider)?; // For HTTPS Azure URLs the first path segment is the container, already baked into the // ObjectStore. Strip it so the resulting path is relative to the container. let path_str = url.path().strip_prefix('/').unwrap_or(""); let path_str = if url.scheme() == "https" - && matches!(provider, Some(CloudProvider::Azure)) + && matches!(provider, CloudProvider::Azure) { path_str.split_once('/').map(|(_, rest)| rest).unwrap_or("") } else { diff --git a/crates/timsrust-centroid/Cargo.toml b/crates/timsrust-centroid/Cargo.toml index 79c0d44..20b640b 100644 --- a/crates/timsrust-centroid/Cargo.toml +++ b/crates/timsrust-centroid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-centroid" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Centroiding algorithms for timsTOF ion mobility spectra" license = "Apache-2.0" diff --git a/crates/timsrust-core/Cargo.toml b/crates/timsrust-core/Cargo.toml index b1a2c71..50397fd 100644 --- a/crates/timsrust-core/Cargo.toml +++ b/crates/timsrust-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-core" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Core data types and abstractions for timsTOF data" license = "Apache-2.0" diff --git a/crates/timsrust-mgf/Cargo.toml b/crates/timsrust-mgf/Cargo.toml index c0ea16d..1617ca8 100644 --- a/crates/timsrust-mgf/Cargo.toml +++ b/crates/timsrust-mgf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-mgf" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "MGF (Mascot Generic Format) writer for timsTOF spectra" license = "Apache-2.0" diff --git a/crates/timsrust-minitdf/Cargo.toml b/crates/timsrust-minitdf/Cargo.toml index 0be4b4c..3b1b271 100644 --- a/crates/timsrust-minitdf/Cargo.toml +++ b/crates/timsrust-minitdf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-minitdf" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Reader for the ProteoScape miniTDF timsTOF file format" license = "Apache-2.0" diff --git a/crates/timsrust-minitdf/src/precursors.rs b/crates/timsrust-minitdf/src/precursors.rs index 84c9ee4..0d0ed6b 100644 --- a/crates/timsrust-minitdf/src/precursors.rs +++ b/crates/timsrust-minitdf/src/precursors.rs @@ -19,7 +19,7 @@ impl MiniTDFPrecursorReader { pub fn new(path: &MiniTDFPath) -> Result { let minitdf_path = path.ms2_parquet().clone(); let parquet_precursors = - ParquetReader::::from(minitdf_path.as_str()) + ParquetReader::::from(minitdf_path.as_ref()) .map_err(MiniTDFPrecursorReaderError::from)? .read_all() .map_err(MiniTDFPrecursorReaderError::from)?; diff --git a/crates/timsrust-minitdf/src/spectrum.rs b/crates/timsrust-minitdf/src/spectrum.rs index cde24c6..719fecd 100644 --- a/crates/timsrust-minitdf/src/spectrum.rs +++ b/crates/timsrust-minitdf/src/spectrum.rs @@ -23,7 +23,7 @@ impl MiniTDFSpectrumReader { let precursor_reader = MiniTDFPrecursorReader::new(path)?; let minitdf_path = path.ms2_parquet().clone(); let all_precursors = - ParquetReader::::from(minitdf_path.as_str()) + ParquetReader::::from(minitdf_path.as_ref()) .map_err(MiniTDFSpectrumReaderError::from)? .read_all() .map_err(MiniTDFSpectrumReaderError::from)?; diff --git a/crates/timsrust-minitdf/src/tdf_blob.rs b/crates/timsrust-minitdf/src/tdf_blob.rs index 4dc68d4..b3b4567 100644 --- a/crates/timsrust-minitdf/src/tdf_blob.rs +++ b/crates/timsrust-minitdf/src/tdf_blob.rs @@ -49,7 +49,7 @@ struct TdfBinFileReader { impl TdfBinFileReader { fn new(path: &MiniTDFPath) -> Result { let bin_path = path.ms2_bin(); - let binary_file = BinaryReader::from(bin_path.as_str())?; + let binary_file = BinaryReader::from(bin_path.as_ref())?; let reader = Self { binary_file, global_file_offset: 0, diff --git a/crates/timsrust-minitdf/src/timstof.rs b/crates/timsrust-minitdf/src/timstof.rs index 0f1eab1..2d989fe 100644 --- a/crates/timsrust-minitdf/src/timstof.rs +++ b/crates/timsrust-minitdf/src/timstof.rs @@ -8,8 +8,8 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MiniTDFPath { uri: Uri, - bin: String, - parquet: String, + bin: Uri, + parquet: Uri, } impl MiniTDFPath { @@ -17,13 +17,11 @@ impl MiniTDFPath { let uri = Uri::from(path.as_ref()); let bin_uri = uri.join("ms2spectrum.bin"); let parquet_uri = uri.join("ms2spectrum.parquet"); - if bin_uri.is_file().unwrap_or(false) - && parquet_uri.is_file().unwrap_or(false) - { + if bin_uri.probe_is_file() && parquet_uri.probe_is_file() { return Ok(Self { uri, - bin: bin_uri.to_string(), - parquet: parquet_uri.to_string(), + bin: bin_uri, + parquet: parquet_uri, }); } match uri.parent() { @@ -33,11 +31,11 @@ impl MiniTDFPath { } } - pub fn ms2_bin(&self) -> &String { + pub fn ms2_bin(&self) -> &Uri { &self.bin } - pub fn ms2_parquet(&self) -> &String { + pub fn ms2_parquet(&self) -> &Uri { &self.parquet } diff --git a/crates/timsrust-parquet-spectra/Cargo.toml b/crates/timsrust-parquet-spectra/Cargo.toml index f0f211d..10759ca 100644 --- a/crates/timsrust-parquet-spectra/Cargo.toml +++ b/crates/timsrust-parquet-spectra/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-parquet-spectra" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Parquet-backed spectrum and precursor reader for timsTOF data" license = "Apache-2.0" diff --git a/crates/timsrust-parquet-spectra/src/parquet_path.rs b/crates/timsrust-parquet-spectra/src/parquet_path.rs index a72acd3..0ff35f1 100644 --- a/crates/timsrust-parquet-spectra/src/parquet_path.rs +++ b/crates/timsrust-parquet-spectra/src/parquet_path.rs @@ -4,8 +4,8 @@ use timsrust_core::utils::custom_error; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ParquetSpectrumPath { uri: Uri, - fragment_path: String, - precursor_path: String, + fragment_path: Uri, + precursor_path: Uri, } impl ParquetSpectrumPath { @@ -15,13 +15,11 @@ impl ParquetSpectrumPath { let uri = Uri::from(path.as_ref()); let fragment_uri = uri.join("fragments.parquet"); let precursor_uri = uri.join("precursors.parquet"); - if fragment_uri.exists().unwrap_or(false) - && precursor_uri.exists().unwrap_or(false) - { + if fragment_uri.probe_is_file() && precursor_uri.probe_is_file() { return Ok(Self { uri, - fragment_path: fragment_uri.as_ref().to_string(), - precursor_path: precursor_uri.as_ref().to_string(), + fragment_path: fragment_uri, + precursor_path: precursor_uri, }); } match uri.parent() { @@ -34,11 +32,11 @@ impl ParquetSpectrumPath { } } - pub fn fragment_path(&self) -> &String { + pub fn fragment_path(&self) -> &Uri { &self.fragment_path } - pub fn precursor_path(&self) -> &String { + pub fn precursor_path(&self) -> &Uri { &self.precursor_path } diff --git a/crates/timsrust-sdk/Cargo.toml b/crates/timsrust-sdk/Cargo.toml index bea14e6..7cf170e 100644 --- a/crates/timsrust-sdk/Cargo.toml +++ b/crates/timsrust-sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-sdk" -version = "0.1.3" +version = "0.1.4" edition = "2024" build = "build.rs" description = "Bruker TimsData SDK bindings for native timsTOF data access" diff --git a/crates/timsrust-tdf/Cargo.toml b/crates/timsrust-tdf/Cargo.toml index 12ed393..fdb4d40 100644 --- a/crates/timsrust-tdf/Cargo.toml +++ b/crates/timsrust-tdf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-tdf" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Reader for the Bruker TDF timsTOF file format (.d folders)" license = "Apache-2.0" diff --git a/crates/timsrust-tsf/Cargo.toml b/crates/timsrust-tsf/Cargo.toml index 623cfa2..0156ef2 100644 --- a/crates/timsrust-tsf/Cargo.toml +++ b/crates/timsrust-tsf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-tsf" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Reader for the Bruker TSF timsTOF file format (MALDI/MS imaging)" license = "Apache-2.0" diff --git a/crates/timsrust-tsf/src/blobs.rs b/crates/timsrust-tsf/src/blobs.rs index 67a0857..d935a9b 100644 --- a/crates/timsrust-tsf/src/blobs.rs +++ b/crates/timsrust-tsf/src/blobs.rs @@ -20,7 +20,7 @@ impl TsfBlobReader { ) -> Result { let path = path.to_timstof_path()?; let bin_path = path.tsf_bin(); - let binary_file = BinaryReader::from(bin_path.as_str())?; + let binary_file = BinaryReader::from(bin_path.as_ref())?; let reader = Self { binary_file }; Ok(reader) } diff --git a/crates/timsrust-tsf/src/mz.rs b/crates/timsrust-tsf/src/mz.rs index 7528a37..764178e 100644 --- a/crates/timsrust-tsf/src/mz.rs +++ b/crates/timsrust-tsf/src/mz.rs @@ -2,9 +2,7 @@ use std::{collections::HashMap, str::FromStr}; use serde::Deserialize; use timsrust_core::{ - Converter, Mz, TofIndex, - io::formats::sql::SqlReader, - utils::simple_error, + Converter, Mz, TofIndex, io::formats::sql::SqlReader, utils::simple_error, }; use crate::TSFPathLike; @@ -38,7 +36,7 @@ impl Tof2MzConverter { pub fn new(path: impl TSFPathLike) -> Self { let timstof_path = path.to_timstof_path().unwrap(); - let reader = SqlReader::from(timstof_path.tsf().as_str()).unwrap(); + let reader = SqlReader::from(timstof_path.tsf().as_ref()).unwrap(); let hash_map: HashMap = reader .from_table::("GlobalMetadata") .unwrap() diff --git a/crates/timsrust-tsf/src/spectrum.rs b/crates/timsrust-tsf/src/spectrum.rs index e07d5ce..55ee327 100644 --- a/crates/timsrust-tsf/src/spectrum.rs +++ b/crates/timsrust-tsf/src/spectrum.rs @@ -45,7 +45,7 @@ impl TSFSpectrumReader { let blob_reader = TsfBlobReader::new(&path)?; let timstof_path = path.to_timstof_path()?; let mz_converter = Tof2MzConverter::new(timstof_path.as_ref()); - let reader = SqlReader::from(timstof_path.tsf().as_str())?; + let reader = SqlReader::from(timstof_path.tsf().as_ref())?; let metadata: HashMap = reader .from_table::("GlobalMetadata")? .read_all()? diff --git a/crates/timsrust-tsf/src/timstof.rs b/crates/timsrust-tsf/src/timstof.rs index 2d7aeb1..237c88d 100644 --- a/crates/timsrust-tsf/src/timstof.rs +++ b/crates/timsrust-tsf/src/timstof.rs @@ -3,8 +3,8 @@ use timsrust_core::io::Uri; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TSFPath { uri: Uri, - tsf: String, - tsf_bin: String, + tsf: Uri, + tsf_bin: Uri, } impl TSFPath { @@ -12,12 +12,8 @@ impl TSFPath { let uri = Uri::from(path.as_ref()); let tsf = uri.join("analysis.tsf"); let tsf_bin = uri.join("analysis.tsf_bin"); - if tsf.is_file().unwrap_or(false) && tsf_bin.is_file().unwrap_or(false) { - return Ok(Self { - uri, - tsf: tsf.to_string(), - tsf_bin: tsf_bin.to_string(), - }); + if tsf.probe_is_file() && tsf_bin.probe_is_file() { + return Ok(Self { uri, tsf, tsf_bin }); } match uri.parent() { Some(parent) => Self::new(parent.as_ref()) @@ -26,11 +22,11 @@ impl TSFPath { } } - pub fn tsf(&self) -> &String { + pub fn tsf(&self) -> &Uri { &self.tsf } - pub fn tsf_bin(&self) -> &String { + pub fn tsf_bin(&self) -> &Uri { &self.tsf_bin } diff --git a/crates/timsrust-utils/Cargo.toml b/crates/timsrust-utils/Cargo.toml index dcb440e..7311590 100644 --- a/crates/timsrust-utils/Cargo.toml +++ b/crates/timsrust-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-utils" -version = "0.1.3" +version = "0.1.4" edition = "2024" description = "Shared utilities for the timsrust crate family (readers, buffers, threading helpers)" license = "Apache-2.0" diff --git a/crates/timsrust/Cargo.toml b/crates/timsrust/Cargo.toml index 62095c1..5e4fca4 100644 --- a/crates/timsrust/Cargo.toml +++ b/crates/timsrust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust" -version = "0.5.3" +version = "0.5.4" edition = "2024" description = "A crate to read Bruker timsTOF data" license = "Apache-2.0" @@ -21,6 +21,7 @@ timsrust-core = { workspace = true, features = ["io"] } timsrust-minitdf = { workspace = true } timsrust-parquet-spectra = { workspace = true } timsrust-tdf = { workspace = true } +timsrust-tsf = { workspace = true } thiserror = { workspace = true } rayon = { workspace = true } diff --git a/crates/timsrust/src/converters.rs b/crates/timsrust/src/converters.rs index 92378df..f51c5f5 100644 --- a/crates/timsrust/src/converters.rs +++ b/crates/timsrust/src/converters.rs @@ -12,6 +12,7 @@ pub enum MzConverter { Bps(timsrust_patched::Tof2MzConverter), Bit(timsrust_core::BitConverter), Tdf(timsrust_tdf::Tof2MzConverter), + TSF(timsrust_tsf::Tof2MzConverter), MiniTdf, } @@ -41,6 +42,9 @@ impl MzConverter { TimsTofFileType::Parquet(_) => { Some(MzConverter::Bit(timsrust_core::BitConverter())) }, + TimsTofFileType::Tsf(_) => Some(MzConverter::TSF( + timsrust_tsf::Tof2MzConverter::new(timstof.as_ref()), + )), } } } @@ -54,6 +58,7 @@ impl Converter for MzConverter { Self::Sdk(converter) => converter.convert(tof_index), Self::Bit(converter) => converter.convert(tof_index), Self::Tdf(converter) => converter.convert(tof_index), + Self::TSF(converter) => converter.convert(tof_index), Self::MiniTdf => timsrust_core::BitConverter().convert(tof_index), } } @@ -68,6 +73,7 @@ impl Converter for MzConverter { Self::Sdk(converter) => converter.convert(mz), Self::Bit(converter) => converter.convert(mz), Self::Tdf(converter) => converter.convert(mz), + Self::TSF(converter) => converter.convert(mz), Self::MiniTdf => timsrust_core::BitConverter().convert(mz), } } @@ -110,6 +116,9 @@ impl ImConverter { TimsTofFileType::Parquet(_) => { Some(ImConverter::Bit(timsrust_core::BitConverter())) }, + TimsTofFileType::Tsf(_) => { + Some(ImConverter::Bit(timsrust_core::BitConverter())) + }, } } } @@ -160,6 +169,9 @@ impl RtConverter { TimsTofFileType::Parquet(_) => { Some(RtConverter::Bit(timsrust_core::BitConverter())) }, + TimsTofFileType::Tsf(_) => { + Some(RtConverter::Bit(timsrust_core::BitConverter())) + }, } } } diff --git a/crates/timsrust/src/lib.rs b/crates/timsrust/src/lib.rs index b3184ae..11f9291 100644 --- a/crates/timsrust/src/lib.rs +++ b/crates/timsrust/src/lib.rs @@ -19,4 +19,9 @@ pub use timstof::{ pub use timsrust_centroid as centroid; pub use timsrust_core as core; pub use timsrust_minitdf as minidf; +#[cfg(feature = "patched")] +pub use timsrust_patched as patched; +#[cfg(feature = "sdk")] +pub use timsrust_sdk as sdk; pub use timsrust_tdf as tdf; +pub use timsrust_tsf as tsf; diff --git a/crates/timsrust/src/precursor_reader.rs b/crates/timsrust/src/precursor_reader.rs index 5cc2c14..ee433ea 100644 --- a/crates/timsrust/src/precursor_reader.rs +++ b/crates/timsrust/src/precursor_reader.rs @@ -118,6 +118,9 @@ impl PrecursorReaderBuilder { TimsTofFileType::Parquet(parquet_path) => Inner::ParquetSpectra( ParquetPrecursorReader::new(parquet_path.precursor_path()), ), + TimsTofFileType::Tsf(_) => { + return Err(PrecursorReaderError::TsfNotSupported); + }, }; let reader = PrecursorReader { precursor_reader }; Ok(reader) @@ -137,4 +140,6 @@ pub enum PrecursorReaderError { NoPath, #[error("{0}")] TimsTofPathError(#[from] TimsTofPathError), + #[error("TSF datasets do not provide precursor information")] + TsfNotSupported, } diff --git a/crates/timsrust/src/spectrum_reader.rs b/crates/timsrust/src/spectrum_reader.rs index a7c66c9..2b525bc 100644 --- a/crates/timsrust/src/spectrum_reader.rs +++ b/crates/timsrust/src/spectrum_reader.rs @@ -8,6 +8,7 @@ use timsrust_tdf::{FrameInfoReader, Metadata, TdfIonReader}; use timsrust_tdf::{ SpectrumReaderConfig, TDFSpectrumReader, TDFSpectrumReaderError, }; +use timsrust_tsf::{TSFSpectrumReader, TSFSpectrumReaderError}; use crate::{ ImConverter, TimsTofPath, TimsTofPathLike, converters::MzConverter, @@ -30,6 +31,7 @@ enum Inner { ParquetSpectra( timsrust_parquet_spectra::spectrum_reader::ParquetSpectrumReader, ), + Tsf(TSFSpectrumReader), } impl Inner { @@ -44,6 +46,7 @@ impl Inner { Inner::Tdf(reader) => Ok(reader.get(index)?), Inner::MiniTdf(reader) => Ok(reader.get(index)?), Inner::ParquetSpectra(reader) => Ok(reader.get(index)?), + Inner::Tsf(reader) => Ok(reader.get(index)?), } } @@ -53,6 +56,7 @@ impl Inner { Inner::MiniTdf(reader) => reader.len(), Inner::Centroider(reader) => reader.len(), Inner::ParquetSpectra(reader) => reader.len(), + Inner::Tsf(reader) => reader.len(), } } @@ -70,6 +74,7 @@ impl Inner { Inner::Tdf(reader) => A::Tdf(reader), Inner::MiniTdf(reader) => A::MiniTdf(reader), Inner::ParquetSpectra(reader) => A::ParquetSpectra(reader), + Inner::Tsf(reader) => A::Tsf(reader), } } } @@ -88,6 +93,7 @@ enum A<'a> { ParquetSpectra( &'a timsrust_parquet_spectra::spectrum_reader::ParquetSpectrumReader, ), + Tsf(&'a TSFSpectrumReader), } impl<'a> ParallelIterator for A<'a> { @@ -119,6 +125,11 @@ impl<'a> ParallelIterator for A<'a> { ) .drive_unindexed(consumer) }, + Self::Tsf(reader) => rayon::iter::ParallelIterator::filter_map( + reader.par_iter(), + |s| s.ok(), + ) + .drive_unindexed(consumer), } } } @@ -234,6 +245,8 @@ pub enum SpectrumReaderError { #[from] timsrust_parquet_spectra::spectrum_reader::ParquetSpectrumReaderError, ), + #[error("{0}")] + TSFSpectrumReaderError(#[from] TSFSpectrumReaderError), #[error("No path provided")] NoPath, #[error("Centroider is not supported")] @@ -327,6 +340,9 @@ impl SpectrumReaderBuilder { ), ) }, + TimsTofFileType::Tsf(tsf_path) => { + Inner::Tsf(TSFSpectrumReader::new(tsf_path)?) + }, }; let mz_converter = Arc::new(MzConverter::new(&path).unwrap()); let mut reader = SpectrumReader { diff --git a/crates/timsrust/src/timstof.rs b/crates/timsrust/src/timstof.rs index f1b90ad..c1e970e 100644 --- a/crates/timsrust/src/timstof.rs +++ b/crates/timsrust/src/timstof.rs @@ -4,6 +4,7 @@ use timsrust_core::io::Uri; use timsrust_minitdf::MiniTDFPath; use timsrust_parquet_spectra::parquet_path::ParquetSpectrumPath; use timsrust_tdf::{FrameReaderError, TDFPath, TdfFrameReader}; +use timsrust_tsf::TSFPath; use crate::{ ImConverter, MzConverter, RtConverter, @@ -19,8 +20,8 @@ use crate::{ pub(crate) enum TimsTofFileType { MiniTdf(MiniTDFPath), Tdf(TDFPath), - Parquet(ParquetSpectrumPath), + Tsf(TSFPath), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -31,24 +32,33 @@ pub struct TimsTofPath { impl TimsTofPath { pub fn new(path: impl AsRef) -> Result { - if let Ok(tdf) = TDFPath::new(&path) { - return Ok(Self { - uri: tdf.uri().clone(), - file_type: TimsTofFileType::Tdf(tdf), - }); - } - if let Ok(minitdf) = MiniTDFPath::new(&path) { - return Ok(Self { - uri: minitdf.uri().clone(), - file_type: TimsTofFileType::MiniTdf(minitdf), - }); - } - - if let Ok(parquet) = ParquetSpectrumPath::new(&path) { - return Ok(Self { - uri: parquet.uri().clone(), - file_type: TimsTofFileType::Parquet(parquet), - }); + let uri = Uri::from(path.as_ref()).local_representation(); + for path in [uri.as_ref(), path.as_ref()] { + if let Ok(tdf) = TDFPath::new(path) { + return Ok(Self { + uri: tdf.uri().clone(), + file_type: TimsTofFileType::Tdf(tdf), + }); + } + if let Ok(tsf) = TSFPath::new(path) { + return Ok(Self { + uri: tsf.uri().clone(), + file_type: TimsTofFileType::Tsf(tsf), + }); + } + if let Ok(minitdf) = MiniTDFPath::new(path) { + return Ok(Self { + uri: minitdf.uri().clone(), + file_type: TimsTofFileType::MiniTdf(minitdf), + }); + } + + if let Ok(parquet) = ParquetSpectrumPath::new(path) { + return Ok(Self { + uri: parquet.uri().clone(), + file_type: TimsTofFileType::Parquet(parquet), + }); + } } Err(TimsTofPathError::UnknownType(PathBuf::from(path.as_ref()))) } diff --git a/python/timsrust-pyo3/Cargo.toml b/python/timsrust-pyo3/Cargo.toml index da10c12..ebb91e1 100644 --- a/python/timsrust-pyo3/Cargo.toml +++ b/python/timsrust-pyo3/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "timsrust-pyo3" -version = "0.1.3" +version = "0.1.4" edition = "2024" publish = false