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}