Initial commit
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
//! # rtx-neuro-io
|
||||
//!
|
||||
//! File format readers and writers for neuroimaging data.
|
||||
//!
|
||||
//! ## Supported Formats
|
||||
//!
|
||||
//! ### EEG Formats
|
||||
//! - **EDF/EDF+** - European Data Format (most common research format)
|
||||
//! - **BDF** - BioSemi 24-bit variant
|
||||
//! - **BrainVision** - .vhdr/.vmrk/.eeg files
|
||||
//! - **EGI** - Electrical Geodesics (.raw, .mff)
|
||||
//!
|
||||
//! ### MEG Formats
|
||||
//! - **FIF** - Elekta/Neuromag format
|
||||
//! - **CTF** - CTF MEG Systems (.ds directories)
|
||||
//! - **BTi/4D** - 4D-Neuroimaging/BTi systems
|
||||
//! - **KIT** - Yokogawa/KIT/Ricoh systems (.con, .sqd)
|
||||
//!
|
||||
//! ### Dataset Formats
|
||||
//! - **BIDS** - Brain Imaging Data Structure (directory-based)
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use rtx_neuro_io::edf::EdfReader;
|
||||
//! use rtx_neuro_io::fif::FifReader;
|
||||
//! use rtx_neuro_io::ctf::CtfReader;
|
||||
//! use rtx_neuro_io::bti::BtiReader;
|
||||
//! use rtx_neuro_io::kit::KitReader;
|
||||
//! use rtx_neuro_io::egi::EgiReader;
|
||||
//!
|
||||
//! // Read EDF file
|
||||
//! let reader = EdfReader::open("recording.edf")?;
|
||||
//! let data = reader.read_data(0.0, 10.0)?; // Read 10 seconds
|
||||
//!
|
||||
//! // Read FIF file (Elekta/Neuromag MEG)
|
||||
//! let reader = FifReader::open("sample_raw.fif")?;
|
||||
//! let data = reader.read_data(0.0, 10.0)?;
|
||||
//!
|
||||
//! // Read CTF dataset (directory-based)
|
||||
//! let reader = CtfReader::open("experiment.ds")?;
|
||||
//! let data = reader.read_data(0.0, 10.0)?;
|
||||
//!
|
||||
//! // Read 4D-Neuroimaging/BTi data
|
||||
//! let reader = BtiReader::open("subject_data")?;
|
||||
//! let data = reader.read_data(0.0, 10.0)?;
|
||||
//!
|
||||
//! // Read Yokogawa/KIT data
|
||||
//! let reader = KitReader::open("recording.con")?;
|
||||
//! let data = reader.read_data(0.0, 10.0)?;
|
||||
//!
|
||||
//! // Read EGI data
|
||||
//! let reader = EgiReader::open("recording.raw")?;
|
||||
//! let data = reader.read_data(0.0, 10.0)?;
|
||||
//!
|
||||
//! // Open BIDS dataset
|
||||
//! use rtx_neuro_io::bids::BidsDataset;
|
||||
//! let dataset = BidsDataset::open("my_bids_dataset")?;
|
||||
//! println!("Dataset: {}", dataset.name());
|
||||
//! for subject in dataset.subject_labels() {
|
||||
//! println!(" Subject: {}", subject);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod bids;
|
||||
pub mod brainvision;
|
||||
pub mod bti;
|
||||
pub mod ctf;
|
||||
pub mod edf;
|
||||
pub mod egi;
|
||||
pub mod fif;
|
||||
pub mod kit;
|
||||
|
||||
// Re-export main types
|
||||
pub use bids::{
|
||||
BidsDataset, BidsFile, BidsSession, BidsSubject, DatasetDescription, FileEntities,
|
||||
is_bids_dataset, parse_bids_filename,
|
||||
};
|
||||
pub use brainvision::BrainVisionReader;
|
||||
pub use bti::{BtiChannel, BtiChannelKind, BtiConfig, BtiReader};
|
||||
pub use ctf::{CtfChannel, CtfChannelKind, CtfReader, Res4Header};
|
||||
pub use edf::{EdfHeader, EdfReader};
|
||||
pub use egi::{EgiChannel, EgiChannelKind, EgiFormat, EgiHeader, EgiReader};
|
||||
pub use fif::{FifChannel, FifInfo, FifReader};
|
||||
pub use kit::{KitChannel, KitChannelKind, KitHeader, KitReader};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
/// Error types for I/O operations
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum IoError {
|
||||
/// File I/O error
|
||||
#[error("File I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
/// Invalid file format
|
||||
#[error("Invalid file format: {0}")]
|
||||
InvalidFormat(String),
|
||||
|
||||
/// Unsupported format version
|
||||
#[error("Unsupported format version: {0}")]
|
||||
UnsupportedVersion(String),
|
||||
|
||||
/// Header parsing error
|
||||
#[error("Header parsing error: {0}")]
|
||||
HeaderParse(String),
|
||||
|
||||
/// Data parsing error
|
||||
#[error("Data parsing error: {0}")]
|
||||
DataParse(String),
|
||||
|
||||
/// File not found
|
||||
#[error("File not found: {0}")]
|
||||
FileNotFound(String),
|
||||
|
||||
/// Channel not found
|
||||
#[error("Channel not found: {0}")]
|
||||
ChannelNotFound(String),
|
||||
}
|
||||
|
||||
/// Result type for I/O operations
|
||||
pub type IoResult<T> = Result<T, IoError>;
|
||||
|
||||
/// Trait for file format readers
|
||||
pub trait NeuroReader {
|
||||
/// Read file header/metadata
|
||||
fn read_header(&mut self) -> IoResult<()>;
|
||||
|
||||
/// Get sampling frequency in Hz
|
||||
fn sfreq(&self) -> f64;
|
||||
|
||||
/// Get number of channels
|
||||
fn n_channels(&self) -> usize;
|
||||
|
||||
/// Get total number of samples per channel
|
||||
fn n_samples(&self) -> usize;
|
||||
|
||||
/// Get channel names
|
||||
fn channel_names(&self) -> Vec<String>;
|
||||
|
||||
/// Read raw data for specified time range
|
||||
/// Returns data as [n_channels x n_samples]
|
||||
fn read_raw_data(&mut self, tmin: f64, tmax: f64) -> IoResult<Vec<f64>>;
|
||||
|
||||
/// Read all data
|
||||
fn read_all_data(&mut self) -> IoResult<Vec<f64>> {
|
||||
let duration = self.n_samples() as f64 / self.sfreq();
|
||||
self.read_raw_data(0.0, duration)
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect file format from path
|
||||
pub fn detect_format(path: impl AsRef<Path>) -> Option<FileFormat> {
|
||||
let path = path.as_ref();
|
||||
|
||||
// Check for directory-based formats
|
||||
if path.is_dir() {
|
||||
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if name.ends_with(".ds") {
|
||||
return Some(FileFormat::Ctf);
|
||||
}
|
||||
if name.ends_with(".mff") {
|
||||
return Some(FileFormat::Egi);
|
||||
}
|
||||
// Check for BTi directory (contains config file)
|
||||
let config_path = path.join("config");
|
||||
if config_path.exists() {
|
||||
return Some(FileFormat::Bti);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check file extension
|
||||
let ext = path.extension()?.to_str()?.to_lowercase();
|
||||
|
||||
match ext.as_str() {
|
||||
"edf" => Some(FileFormat::Edf),
|
||||
"bdf" => Some(FileFormat::Bdf),
|
||||
"vhdr" => Some(FileFormat::BrainVision),
|
||||
"set" => Some(FileFormat::EegLab),
|
||||
"fif" => Some(FileFormat::Fif),
|
||||
"ds" => Some(FileFormat::Ctf),
|
||||
"cnt" => Some(FileFormat::Neuroscan),
|
||||
"nwb" => Some(FileFormat::Nwb),
|
||||
"con" | "sqd" => Some(FileFormat::Kit),
|
||||
"raw" => Some(FileFormat::Egi),
|
||||
"mff" => Some(FileFormat::Egi),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Supported file formats
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FileFormat {
|
||||
/// EDF (European Data Format)
|
||||
Edf,
|
||||
/// BDF (BioSemi 24-bit)
|
||||
Bdf,
|
||||
/// BrainVision (.vhdr/.vmrk/.eeg)
|
||||
BrainVision,
|
||||
/// EEGLAB (.set)
|
||||
EegLab,
|
||||
/// Elekta/Neuromag FIF
|
||||
Fif,
|
||||
/// CTF MEG
|
||||
Ctf,
|
||||
/// 4D-Neuroimaging/BTi MEG
|
||||
Bti,
|
||||
/// Yokogawa/KIT/Ricoh MEG
|
||||
Kit,
|
||||
/// EGI (.raw, .mff)
|
||||
Egi,
|
||||
/// Neuroscan (.cnt)
|
||||
Neuroscan,
|
||||
/// Neurodata Without Borders
|
||||
Nwb,
|
||||
}
|
||||
Reference in New Issue
Block a user