bids_core/lib.rs
1#![deny(unsafe_code)]
2//! Core types and abstractions for working with BIDS datasets.
3//!
4//! This crate provides the foundational building blocks used by all other `bids-*` crates:
5//!
6//! - [`Entity`] and [`EntityValue`] — BIDS entity definitions (subject, session, task, run, …)
7//! with regex-based extraction from file paths and typed value coercion.
8//! - [`BidsFile`] — Representation of a single file in a BIDS dataset, with automatic
9//! file type detection, entity extraction, suffix/extension parsing, and companion
10//! file lookup.
11//! - [`BidsMetadata`] — Ordered key-value metadata dictionary backed by `IndexMap`,
12//! supporting typed access (`get_f64`, `get_str`, `get_array`, …) and deserialization
13//! into arbitrary structs.
14//! - [`DatasetDescription`] — Typed representation of `dataset_description.json` with
15//! validation, derivative detection, and legacy field migration.
16//! - [`Config`] — Layout configuration defining entity patterns and path templates,
17//! loadable from built-in configs (`bids`, `derivatives`) or custom JSON files.
18//! - [`PaddedInt`] — Zero-padded integer type that preserves formatting (e.g., `"02"`)
19//! while comparing numerically.
20//! - [`BidsError`] — Comprehensive error enum covering I/O, JSON, validation, entity,
21//! filter, database, and path-building errors.
22//!
23//! # BIDS Entities
24//!
25//! BIDS filenames encode metadata as key-value pairs separated by underscores:
26//!
27//! ```text
28//! sub-01_ses-02_task-rest_run-01_bold.nii.gz
29//! ^^^^^^ ^^^^^^ ^^^^^^^^^ ^^^^^^ ^^^^
30//! subject session task run suffix
31//! ```
32//!
33//! The [`mod@entities`] module defines the canonical entity ordering and provides
34//! functions to parse entities from paths, sort them, and coerce values to
35//! the correct types (string, padded integer, float, boolean).
36//!
37//! # Example
38//!
39//! ```
40//! use bids_core::{BidsFile, Config, DatasetDescription};
41//!
42//! // Parse entities from a filename
43//! let config = Config::bids();
44//! let entities = bids_core::entities::parse_file_entities(
45//! "sub-01/eeg/sub-01_task-rest_eeg.edf",
46//! &config.entities,
47//! );
48//! assert_eq!(entities.get("subject").unwrap().as_str_lossy(), "01");
49//! assert_eq!(entities.get("task").unwrap().as_str_lossy(), "rest");
50//! ```
51
52pub mod config;
53pub mod dataset_description;
54pub mod entities;
55pub mod error;
56pub mod file;
57pub mod genetic;
58pub mod hed;
59pub mod metadata;
60pub mod padded_int;
61pub mod timeseries;
62pub mod utils;
63
64pub use config::Config;
65pub use dataset_description::DatasetDescription;
66pub use entities::{Entities, Entity, EntityValue, StringEntities};
67pub use error::{BidsError, Result};
68pub use file::{BidsFile, CopyMode, FileType};
69pub use metadata::BidsMetadata;
70pub use padded_int::PaddedInt;
71pub use utils::{collect_associated_files, convert_json_keys, matches_entities};
72
73/// Helper for modality crates: try to read from a companion file path.
74///
75/// Returns `Ok(None)` if the file doesn't exist, `Ok(Some(T))` if it does
76/// and is readable, or `Err(...)` on I/O errors.
77pub fn try_read_companion<T, F>(path: &std::path::Path, reader: F) -> Result<Option<T>>
78where
79 F: FnOnce(&std::path::Path) -> Result<T>,
80{
81 if path.exists() {
82 Ok(Some(reader(path)?))
83 } else {
84 Ok(None)
85 }
86}
87
88/// Convenience macro for building an [`Entities`] map inline.
89///
90/// # Example
91///
92/// ```
93/// use bids_core::entities;
94///
95/// let ents = entities! {
96/// "subject" => "01",
97/// "task" => "rest",
98/// "suffix" => "eeg",
99/// "extension" => ".edf",
100/// };
101/// assert_eq!(ents.get("subject").unwrap().as_str_lossy(), "01");
102/// assert_eq!(ents.len(), 4);
103/// ```
104#[macro_export]
105macro_rules! entities {
106 ($($key:expr => $val:expr),* $(,)?) => {{
107 let mut map = $crate::Entities::new();
108 $(
109 map.insert($key.to_string(), $crate::EntityValue::from($val));
110 )*
111 map
112 }};
113}