#![allow(dead_code)]
mod test;
use ndarray::prelude::*;
use std::error::Error;
use std::io::Read;
#[cfg(feature = "download")]
use std::fs::File;
#[cfg(feature = "download")]
use std::path::Path;
#[cfg(feature = "download")]
use tar::Archive;
#[cfg(feature = "display")]
mod display;
#[cfg(feature = "display")]
use crate::display::*;
#[cfg(feature = "display")]
use rand::Rng;
#[derive(Debug)]
pub struct Cifar10<'a> {
base_path: &'a str,
cifar_data_path: &'a str,
show_images: bool,
encode_one_hot: bool,
training_bin_paths: Vec<&'a str>,
testing_bin_paths: Vec<&'a str>,
num_records_train: usize,
num_records_test: usize,
download_and_extract: bool,
}
impl<'a> Cifar10<'a> {
pub fn default() -> Self {
Cifar10 {
base_path: "data/",
cifar_data_path: "cifar-10-batches-bin/",
show_images: false,
encode_one_hot: true,
training_bin_paths: vec![
"data_batch_1.bin",
"data_batch_2.bin",
"data_batch_3.bin",
"data_batch_4.bin",
"data_batch_5.bin",
],
testing_bin_paths: vec!["test_batch.bin"],
num_records_train: 50_000,
num_records_test: 10_000,
download_and_extract: false,
}
}
pub fn base_path(mut self, base_path: &'a str) -> Self {
self.base_path = base_path;
self
}
pub fn cifar_data_path(mut self, cifar_data_path: &'a str) -> Self {
self.cifar_data_path = cifar_data_path;
self
}
pub fn download_and_extract(mut self, download_and_extract: bool) -> Self {
self.download_and_extract = download_and_extract;
self
}
pub fn show_images(mut self, show_images: bool) -> Self {
self.show_images = show_images;
self
}
pub fn encode_one_hot(mut self, encode_one_hot: bool) -> Self {
self.encode_one_hot = encode_one_hot;
self
}
pub fn training_bin_paths(mut self, training_bin_paths: Vec<&'a str>) -> Self {
self.training_bin_paths = training_bin_paths;
self
}
pub fn testing_bin_paths(mut self, testing_bin_paths: Vec<&'a str>) -> Self {
self.testing_bin_paths = testing_bin_paths;
self
}
pub fn num_records_train(mut self, num_records_train: usize) -> Self {
self.num_records_train = num_records_train;
self
}
pub fn num_records_test(mut self, num_records_test: usize) -> Self {
self.num_records_test = num_records_test;
self
}
pub fn build(self) -> Result<(Array4<u8>, Array2<u8>, Array4<u8>, Array2<u8>), Box<dyn Error>> {
#[cfg(feature = "download")]
match self.download_and_extract {
false => (),
true => {
let url = "https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz";
self.download(url, "cifar-10-binary.tar.gz")?;
self.extract("cifar-10-binary.tar.gz")?;
}
}
let (train_data, train_labels) = get_data(&self, "train")?;
let (test_data, test_labels) = get_data(&self, "test")?;
Ok((train_data, train_labels, test_data, test_labels))
}
#[cfg(feature = "download")]
fn download(&self, url: &str, archive_name: &str) -> Result<(), Box<dyn Error>> {
let download_dir = self.base_path;
if !Path::new(&download_dir).exists() {
std::fs::create_dir_all(&download_dir)
.or_else(|e| {
Err(format!(
"Failed to to create directory {:?}: {:?}",
download_dir, e
))
})
.unwrap();
}
let archive = download_dir.to_owned() + archive_name;
if Path::new(&archive).exists() {
println!(" File {:?} already exists, skipping downloading.", archive);
} else {
println!(" Downloading {} to {:?}...", url, download_dir);
let f = std::fs::File::create(&archive)
.or_else(|e| Err(format!("Failed to create file {:?}: {:?}", archive, e)))
.unwrap();
let mut writer = std::io::BufWriter::new(f);
let mut response = reqwest::blocking::get(url)
.expect(format!("Failed to download {:?}", url).as_str());
let _ = std::io::copy(&mut response, &mut writer)
.or_else(|e| Err(format!("Failed to to write to file {:?}: {:?}", archive, e)))
.unwrap();
println!(" Downloading {} to {:?} done!", archive, download_dir);
}
Ok(())
}
#[cfg(feature = "download")]
fn extract(&self, archive_name: &str) -> Result<(), Box<dyn Error>> {
let download_dir = self.base_path;
let archive = download_dir.to_owned() + archive_name;
let extract_to = download_dir.to_owned() + "cifar-10-batches-bin";
if Path::new(&extract_to).exists() {
println!(
" Extracted file {:?} already exists, skipping extraction.",
extract_to
);
} else {
println!("Beginning extraction of {} to {}", archive, extract_to);
use flate2::read::GzDecoder;
let tar_gz = File::open(archive)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(download_dir)?;
}
Ok(())
}
pub fn build_as_flat_f32(
self,
) -> Result<(Array2<f32>, Array2<f32>, Array2<f32>, Array2<f32>), Box<dyn Error>> {
let (train_data, train_labels) = get_data(&self, "train")?;
let (test_data, test_labels) = get_data(&self, "test")?;
let train_labels = train_labels.mapv(|x| x as f32);
let train_data = train_data
.into_shape((self.num_records_train, 32 * 32 * 3))?
.mapv(|x| x as f32 / 256.);
let test_labels = test_labels.mapv(|x| x as f32);
let test_data = test_data
.into_shape((self.num_records_test, 32 * 32 * 3))?
.mapv(|x| x as f32 / 256.);
Ok((train_data, train_labels, test_data, test_labels))
}
}
fn get_data(config: &Cifar10, dataset: &str) -> Result<(Array4<u8>, Array2<u8>), Box<dyn Error>> {
let mut buffer: Vec<u8> = Vec::new();
let (bin_paths, num_records) = match dataset {
"train" => (config.training_bin_paths.clone(), config.num_records_train),
"test" => (config.testing_bin_paths.clone(), config.num_records_test),
_ => panic!("An unexpected value was passed for which dataset should be parsed"),
};
for bin in &bin_paths {
let full_cifar_path = [config.base_path, config.cifar_data_path, bin].join("");
let mut f = std::fs::File::open(full_cifar_path)?;
let mut temp_buffer: Vec<u8> = Vec::new();
f.read_to_end(&mut temp_buffer)?;
buffer.extend(&temp_buffer);
}
let mut labels: Array2<u8> = Array2::zeros((num_records, 10));
labels[[0, buffer[0] as usize]] = 1;
let mut data: Vec<u8> = Vec::with_capacity(num_records * 3072);
for num in 0..num_records {
let base = num * (3073);
let label = buffer[base];
if label > 9 {
panic!(format!(
"Label is {}, which is inconsistent with the CIFAR-10 scheme",
label
));
}
labels[[num, label as usize]] = 1;
data.extend(&buffer[base + 1..=base + 3072]);
}
let data: Array4<u8> = Array::from_shape_vec((num_records, 3, 32, 32), data)?;
if config.show_images {
#[cfg(feature = "display")]
{
let mut rng = rand::thread_rng();
let num: usize = rng.gen_range(0..num_records);
let img_arr = data.slice(s!(num, .., .., ..));
println!(
"Data label: {}",
return_label_from_one_hot(labels.slice(s![num, ..]).to_owned())
);
display_img(&img_arr.to_owned())?;
}
#[cfg(not(feature = "display"))]
{
println!("WARNING: Displaying images disabled.");
println!("Please use the crate's 'display' feature to enable it.");
}
}
Ok((data, labels))
}
fn return_label_from_one_hot(one_hot: Array1<u8>) -> String {
if one_hot == array![1, 0, 0, 0, 0, 0, 0, 0, 0, 0] {
"airplane".to_string()
} else if one_hot == array![0, 1, 0, 0, 0, 0, 0, 0, 0, 0] {
"automobile".to_string()
} else if one_hot == array![0, 0, 1, 0, 0, 0, 0, 0, 0, 0] {
"bird".to_string()
} else if one_hot == array![0, 0, 0, 1, 0, 0, 0, 0, 0, 0] {
"cat".to_string()
} else if one_hot == array![0, 0, 0, 0, 1, 0, 0, 0, 0, 0] {
"deer".to_string()
} else if one_hot == array![0, 0, 0, 0, 0, 1, 0, 0, 0, 0] {
"dog".to_string()
} else if one_hot == array![0, 0, 0, 0, 0, 0, 1, 0, 0, 0] {
"frog".to_string()
} else if one_hot == array![0, 0, 0, 0, 0, 0, 0, 1, 0, 0] {
"horse".to_string()
} else if one_hot == array![0, 0, 0, 0, 0, 0, 0, 0, 1, 0] {
"ship".to_string()
} else if one_hot == array![0, 0, 0, 0, 0, 0, 0, 0, 0, 1] {
"truck".to_string()
} else {
format!("Error: no valid label could be assigned to {}", one_hot)
}
}