intan_importer/lib.rs
1/*!
2# Intan Importer
3
4A Rust library for importing and processing Intan Technologies RHS data files. This crate provides
5strongly-typed interfaces to Intan's neural recording file formats, used in electrophysiology research.
6
7## Overview
8
9Intan Technologies manufactures hardware for neural recording, including systems that record electrical signals
10from the brain and nervous system. These systems generate data files in proprietary formats (RHS/RHD).
11This library provides tools to read and process these files, making the data accessible for analysis.
12
13## Main Features
14
15- **Single File or Directory Loading**: Load individual RHS files or automatically combine multiple files from a directory
16- **Comprehensive Parsing**: Read and parse Intan RHS files with full support for all sections
17- **Strong Typing**: All data structures are strongly typed with appropriate Rust types
18- **Automatic Processing**:
19 - Signal scaling to meaningful units (μV, V)
20 - Notch filter application for line noise removal
21 - Timestamp alignment and verification
22- **Efficient Implementation**:
23 - Fast binary parsing with minimal allocations
24 - Proper error handling with descriptive messages
25 - Memory-efficient data structures
26
27## Quick Start
28
29```rust
30use intan_importer::load;
31
32// Load single RHS file
33let result = load("path/to/your/file.rhs");
34
35// Or load all RHS files from a directory
36let result = load("path/to/recording/directory");
37
38match result {
39 Ok(rhs_file) => {
40 // Access header information
41 println!("Sample rate: {} Hz", rhs_file.header.sample_rate);
42 println!("Number of channels: {}", rhs_file.header.amplifier_channels.len());
43
44 // Access recording data if present
45 if rhs_file.data_present {
46 if let Some(data) = &rhs_file.data {
47 if let Some(amp_data) = &data.amplifier_data {
48 // Process amplifier data
49 println!("Data dimensions: {:?}", amp_data.shape());
50 }
51 }
52 }
53 },
54 Err(e) => println!("Error loading file: {}", e),
55}
56```
57
58## Data Structure
59
60The crate organizes Intan data into a hierarchy of structs:
61
62- `RhsFile`: Top-level container with header and data
63 - `RhsHeader`: Configuration, channel info, and recording parameters
64 - `Version`, `Notes`, `FrequencyParameters`, etc.
65 - Lists of channels: `amplifier_channels`, `board_adc_channels`, etc.
66 - `RhsData`: Actual recorded signals
67 - `timestamps`, `amplifier_data`, `stim_data`, etc.
68
69## Error Handling
70
71The library provides descriptive errors for various failure scenarios (file format errors,
72I/O failures, etc.) through the `IntanError` type.
73*/
74
75mod reader;
76pub mod types;
77
78use std::error::Error;
79use std::path::Path;
80use std::fs;
81
82// Re-export types
83pub use types::*;
84
85/// Loads RHS data from a file or directory.
86///
87/// This function can handle two input types:
88/// 1. **Single file**: Loads a single RHS file
89/// 2. **Directory**: Loads and combines all RHS files in the directory
90///
91/// When loading from a directory, all RHS files are combined chronologically into
92/// a single dataset. Files must have compatible headers (same channels, sample rates, etc.).
93///
94/// # Parameters
95///
96/// * `path` - Path to an RHS file or a directory containing RHS files
97///
98/// # Returns
99///
100/// * `Result<RhsFile, Box<dyn Error>>` - Either the loaded file data or an error
101///
102/// # Examples
103///
104/// ## Loading a single file
105/// ```no_run
106/// use intan_importer::load;
107///
108/// let result = load("recording.rhs");
109/// match result {
110/// Ok(rhs_file) => {
111/// println!("Loaded {} seconds of data", rhs_file.duration());
112/// },
113/// Err(e) => println!("Error: {}", e),
114/// }
115/// ```
116///
117/// ## Loading from a directory
118/// ```no_run
119/// use intan_importer::load;
120///
121/// // Load all RHS files from a recording session split across multiple files
122/// let result = load("recording_session/");
123/// match result {
124/// Ok(rhs_file) => {
125/// println!("Combined {} files", rhs_file.source_files.as_ref().unwrap().len());
126/// println!("Total duration: {} seconds", rhs_file.duration());
127/// },
128/// Err(e) => println!("Error: {}", e),
129/// }
130/// ```
131///
132/// # Performance Considerations
133///
134/// When loading multiple files, the entire combined dataset is loaded into memory.
135/// Be aware of memory usage when dealing with lengthy recording sessions.
136pub fn load<P: AsRef<Path>>(path: P) -> Result<RhsFile, Box<dyn Error>> {
137 let path = path.as_ref();
138
139 if path.is_file() {
140 // Load single file
141 reader::load_file(path)
142 } else if path.is_dir() {
143 // Load and combine all RHS files in directory
144 load_directory(path)
145 } else {
146 Err(Box::new(IntanError::Other(format!(
147 "Path '{}' is neither a file nor a directory",
148 path.display()
149 ))))
150 }
151}
152
153/// Loads and combines all RHS files from a directory
154fn load_directory<P: AsRef<Path>>(dir_path: P) -> Result<RhsFile, Box<dyn Error>> {
155 let dir_path = dir_path.as_ref();
156
157 // Find all .rhs files in the directory
158 let mut rhs_files: Vec<_> = fs::read_dir(dir_path)?
159 .filter_map(|entry| entry.ok())
160 .filter(|entry| {
161 entry.path().extension()
162 .and_then(|ext| ext.to_str())
163 .map(|ext| ext.eq_ignore_ascii_case("rhs"))
164 .unwrap_or(false)
165 })
166 .map(|entry| entry.path())
167 .collect();
168
169 if rhs_files.is_empty() {
170 return Err(Box::new(IntanError::Other(
171 "No RHS files found in directory".to_string()
172 )));
173 }
174
175 // Sort files by name to ensure consistent ordering
176 rhs_files.sort();
177
178 println!("Found {} RHS files to combine:", rhs_files.len());
179 for file in &rhs_files {
180 println!(" - {}", file.display());
181 }
182
183 // Load and combine the files
184 reader::load_and_combine_files(&rhs_files)
185}