pub const CHANNEL_VOCAB: &[&str] = &[
"A1-T3", "AF3", "AF4", "C1", "C2", "C3", "C3-CZ", "C3-P3",
"C4", "C4-P4", "C4-T4", "C5", "C6", "CB1", "CB2", "CP1",
"CP2", "CP3", "CP4", "CP5", "CP6", "CPZ", "CZ", "CZ-C4",
"F1", "F10", "F2", "F3", "F3-C3", "F4", "F4-C4", "F5",
"F6", "F7", "F7-T3", "F8", "F8-T4", "F9", "FC1", "FC2",
"FC3", "FC4", "FC5", "FC6", "FCZ", "FP1", "FP1-F3", "FP1-F7",
"FP2", "FP2-F4", "FP2-F8", "FPZ", "FT7", "FT8", "FZ", "O1",
"O2", "OZ", "P1", "P2", "P3", "P3-O1", "P4", "P4-O2",
"P5", "P6", "P7", "P8", "PO3", "PO4", "PO5", "PO6",
"PO7", "PO8", "POZ", "PZ", "T3", "T3-C3", "T3-T5", "T4",
"T4-A2", "T4-T6", "T5", "T5-O1", "T6", "T6-O2", "T7", "T8",
"TP7", "TP8",
];
pub fn channel_index(name: &str) -> Option<usize> {
CHANNEL_VOCAB.iter().position(|&v| v == name)
}
pub fn channel_indices(names: &[&str]) -> Vec<Option<usize>> {
names.iter().map(|n| channel_index(n)).collect()
}
pub fn channel_indices_unwrap(names: &[&str]) -> Vec<i64> {
names.iter().map(|n| {
channel_index(n)
.unwrap_or_else(|| panic!("Channel '{}' not in LUNA vocabulary", n)) as i64
}).collect()
}
pub fn channel_name(index: usize) -> Option<&'static str> {
CHANNEL_VOCAB.get(index).copied()
}
pub const VOCAB_SIZE: usize = 90;
pub const TUEG_CHANNELS: &[&str] = &[
"FP1-F7", "F7-T3", "T3-T5", "T5-O1",
"FP2-F8", "F8-T4", "T4-T6", "T6-O2",
"T3-C3", "C3-CZ", "CZ-C4", "C4-T4",
"FP1-F3", "F3-C3", "C3-P3", "P3-O1",
"FP2-F4", "F4-C4", "C4-P4", "P4-O2",
"A1-T3", "T4-A2",
];
pub const SIENA_CHANNELS: &[&str] = &[
"FP1", "FP2", "F3", "C3", "P3", "O1", "F7", "T3", "T5",
"FC1", "FC5", "CP1", "CP5", "F9", "FZ", "CZ", "PZ",
"F4", "C4", "P4", "O2", "F8", "T4", "T6", "FC2", "FC6",
"CP2", "CP6", "F10",
];
pub const SEED_CHANNELS: &[&str] = &[
"FP1", "FPZ", "FP2", "AF3", "AF4", "F7", "F5", "F3", "F1",
"FZ", "F2", "F4", "F6", "F8", "FT7", "FC5", "FC3", "FC1",
"FCZ", "FC2", "FC4", "FC6", "FT8", "T7", "C5", "C3", "C1",
"CZ", "C2", "C4", "C6", "T8", "TP7", "CP5", "CP3", "CP1",
"CPZ", "CP2", "CP4", "CP6", "TP8", "P7", "P5", "P3", "P1",
"PZ", "P2", "P4", "P6", "P8", "PO7", "PO5", "PO3", "POZ",
"PO4", "PO6", "PO8", "CB1", "O1", "OZ", "O2", "CB2",
];
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
#[test]
fn vocab_size_is_90() {
assert_eq!(CHANNEL_VOCAB.len(), VOCAB_SIZE);
}
#[test]
fn vocab_is_sorted() {
for i in 1..CHANNEL_VOCAB.len() {
assert!(CHANNEL_VOCAB[i - 1] < CHANNEL_VOCAB[i],
"CHANNEL_VOCAB not sorted: [{}]='{}' >= [{}]='{}'",
i - 1, CHANNEL_VOCAB[i - 1], i, CHANNEL_VOCAB[i]);
}
}
#[test]
fn vocab_matches_python_union() {
let mut all: BTreeSet<&str> = BTreeSet::new();
for &ch in TUEG_CHANNELS { all.insert(ch); }
for &ch in SIENA_CHANNELS { all.insert(ch); }
for &ch in SEED_CHANNELS { all.insert(ch); }
let expected: Vec<&str> = all.into_iter().collect();
assert_eq!(expected.len(), VOCAB_SIZE, "union size mismatch");
assert_eq!(expected, CHANNEL_VOCAB, "vocab content mismatch");
}
#[test]
fn tueg_indices_valid() {
for &ch in TUEG_CHANNELS {
assert!(channel_index(ch).is_some(), "TUEG channel '{}' not in vocab", ch);
}
}
#[test]
fn siena_indices_valid() {
for &ch in SIENA_CHANNELS {
assert!(channel_index(ch).is_some(), "Siena channel '{}' not in vocab", ch);
}
}
#[test]
fn seed_indices_valid() {
for &ch in SEED_CHANNELS {
assert!(channel_index(ch).is_some(), "SEED channel '{}' not in vocab", ch);
}
}
#[test]
fn round_trip_name_index() {
for (i, &name) in CHANNEL_VOCAB.iter().enumerate() {
assert_eq!(channel_index(name), Some(i));
assert_eq!(channel_name(i), Some(name));
}
}
}