Skip to main content

bids_beh/
lib.rs

1#![deny(unsafe_code)]
2//! Behavioral data support for BIDS datasets.
3//!
4//! Provides access to behavioral task data files (`_beh.tsv`), associated
5//! events files, and dataset summaries for the `beh` datatype.
6//!
7//! See: <https://bids-specification.readthedocs.io/en/stable/modality-specific-files/behavioral-experiments.html>
8
9use bids_core::error::Result;
10use bids_core::file::BidsFile;
11use bids_io::tsv::read_tsv;
12use bids_layout::BidsLayout;
13use std::collections::HashMap;
14
15pub struct BehLayout<'a> {
16    layout: &'a BidsLayout,
17}
18impl<'a> BehLayout<'a> {
19    pub fn new(layout: &'a BidsLayout) -> Self {
20        Self { layout }
21    }
22    pub fn get_beh_files(&self) -> Result<Vec<BidsFile>> {
23        self.layout.get().datatype("beh").collect()
24    }
25    pub fn get_beh_files_for_subject(&self, s: &str) -> Result<Vec<BidsFile>> {
26        self.layout.get().datatype("beh").subject(s).collect()
27    }
28    pub fn get_beh_files_for_task(&self, t: &str) -> Result<Vec<BidsFile>> {
29        self.layout.get().datatype("beh").task(t).collect()
30    }
31    pub fn get_beh_subjects(&self) -> Result<Vec<String>> {
32        self.layout.get().datatype("beh").return_unique("subject")
33    }
34    pub fn get_beh_tasks(&self) -> Result<Vec<String>> {
35        self.layout.get().datatype("beh").return_unique("task")
36    }
37
38    pub fn get_events(&self, f: &BidsFile) -> Result<Option<Vec<HashMap<String, String>>>> {
39        let p = f.companion("events", "tsv");
40        if p.exists() {
41            Ok(Some(read_tsv(&p)?))
42        } else {
43            Ok(None)
44        }
45    }
46
47    pub fn get_beh_data(&self, f: &BidsFile) -> Result<Option<Vec<HashMap<String, String>>>> {
48        if f.filename.ends_with("_beh.tsv") && f.path.exists() {
49            Ok(Some(read_tsv(&f.path)?))
50        } else {
51            Ok(None)
52        }
53    }
54
55    pub fn summary(&self) -> Result<BehSummary> {
56        let files = self.get_beh_files()?;
57        let subjects = self.get_beh_subjects()?;
58        let tasks = self.get_beh_tasks()?;
59        Ok(BehSummary {
60            n_subjects: subjects.len(),
61            n_files: files.len(),
62            subjects,
63            tasks,
64        })
65    }
66}
67
68#[derive(Debug)]
69pub struct BehSummary {
70    pub n_subjects: usize,
71    pub n_files: usize,
72    pub subjects: Vec<String>,
73    pub tasks: Vec<String>,
74}
75impl std::fmt::Display for BehSummary {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        writeln!(
78            f,
79            "Behavioral Summary: {} subjects, {} files, tasks: {:?}",
80            self.n_subjects, self.n_files, self.tasks
81        )
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use std::io::Write;
89
90    #[test]
91    fn test_beh_events() {
92        let dir = std::env::temp_dir().join("bids_beh_test");
93        std::fs::create_dir_all(&dir).unwrap();
94        let path = dir.join("sub-01_task-test_events.tsv");
95        let mut f = std::fs::File::create(&path).unwrap();
96        writeln!(f, "onset\tduration\ttrial_type").unwrap();
97        writeln!(f, "1.0\t0.5\tgo").unwrap();
98        writeln!(f, "2.0\t0.5\tstop").unwrap();
99
100        let rows = read_tsv(&path).unwrap();
101        assert_eq!(rows.len(), 2);
102        assert_eq!(rows[0]["trial_type"], "go");
103        std::fs::remove_dir_all(&dir).unwrap();
104    }
105}