diff --git a/.gitmodules b/.gitmodules index 872e78d2..19ffb64f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "Tools/WinML-Dashboard/deps/Netron"] path = Tools/WinMLDashboard/deps/Netron url = https://github.com/lutzroeder/Netron.git +[submodule "Samples/RustSqueezenet/winrt-rs"] + path = Samples/RustSqueezenet/winrt-rs + url = https://github.com/microsoft/winrt-rs.git diff --git a/Samples/RustSqueezenet/Cargo.toml b/Samples/RustSqueezenet/Cargo.toml new file mode 100644 index 00000000..678b6fb0 --- /dev/null +++ b/Samples/RustSqueezenet/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rust_squeezenet" +version = "0.1.0" +authors = ["Microsoft"] +edition = "2018" + +[build] +target-dir = "target" + +[dependencies] +winrt = { path = "./winrt-rs" } +# NOTE: winrt_macros is needed as a dependency because Rust 1.46 is needed and hasn't been released yet. +winrt_macros = { git = "https://github.com/microsoft/winrt-rs", version = "0.7.2" } + +[build-dependencies] +winrt = { path = "./winrt-rs" } + +# Nuget packages +[package.metadata.winrt.dependencies] +"Microsoft.Windows.SDK.Contracts" = "10.0.19041.1" +"Microsoft.AI.MachineLearning" = "1.4.0" diff --git a/Samples/RustSqueezenet/README.md b/Samples/RustSqueezenet/README.md new file mode 100644 index 00000000..1c35ae6c --- /dev/null +++ b/Samples/RustSqueezenet/README.md @@ -0,0 +1,34 @@ +# SqueezeNet Rust sample +This is a desktop application that uses SqueezeNet, a pre-trained machine learning model, to detect the predominant object in an image selected by the user from a file. + +Note: SqueezeNet was trained to work with image sizes of 224x224, so you must provide an image of size 224X224. + +## Prerequisites +- [Install Rustup](https://www.rust-lang.org/tools/install) +- Install cargo-winrt through command prompt. Until Rust 1.46 is released, cargo-winrt should be installed through the winrt-rs git repository. + - ```cargo install --git https://github.com/microsoft/winrt-rs cargo-winrt``` + +## Build and Run the sample +1. This project requires Rust 1.46, which is currently in Beta. Rust release dates can be found [here](https://forge.rust-lang.org/). Rust Beta features can be enabled by running the following commands through command prompt in this current project directory after installation of Rustup : + - ``` rustup install beta ``` + - ``` rustup override set beta ``` +2. Install the WinRT nuget dependencies with this command: ``` cargo winrt install ``` +3. Build the project by running ```cargo build``` for debug and ```cargo build --release``` for release. +4. Run the sample by running this command through the command prompt. ``` cargo winrt run ``` + - Another option would be to run the executable directly. Should be ```\Samples\RustSqueezeNet\target\debug\rust_squeezenet.exe``` + +## Sample output +``` +C:\Repos\Windows-Machine-Learning\Samples\RustSqueezeNet> cargo winrt run + Finished installing WinRT dependencies in 0.47s + Finished dev [unoptimized + debuginfo] target(s) in 0.12s + Running `target\debug\rust_squeezenet.exe` +Loading model C:\Repos\Windows-Machine-Learning\RustSqueezeNet\target\debug\Squeezenet.onnx +Creating session +Loading image file C:\Repos\Windows-Machine-Learning\RustSqueezeNet\target\debug\kitten_224.png +Evaluating +Results: + tabby tabby cat 0.9314611 + Egyptian cat 0.06530659 + tiger cat 0.0029267797 +``` \ No newline at end of file diff --git a/Samples/RustSqueezenet/build.rs b/Samples/RustSqueezenet/build.rs new file mode 100644 index 00000000..2f262ea0 --- /dev/null +++ b/Samples/RustSqueezenet/build.rs @@ -0,0 +1,35 @@ +macro_rules! copy_file { + ($file:expr, $destination:expr) => { + match fs::copy($file, + $destination) { + Ok(file) => file, + Err(error) => panic!("Problem copying the file {} to {}: {:?}", $file, $destination, error), + }; + } +} + +fn copy_resources() { + use std::fs; + let profile = std::env::var("PROFILE").unwrap(); + if profile == "debug" { + copy_file!("..\\..\\SharedContent\\media\\fish.png",".\\target\\debug\\fish.png"); + copy_file!("..\\..\\SharedContent\\media\\fish.png",".\\target\\debug\\kitten_224.png"); + copy_file!("..\\..\\SharedContent\\models\\SqueezeNet.onnx",".\\target\\debug\\SqueezeNet.onnx"); + copy_file!("..\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt",".\\target\\debug\\Labels.txt"); + } + else if profile == "release" { + copy_file!("..\\..\\SharedContent\\media\\fish.png",".\\target\\release\\fish.png"); + copy_file!("..\\..\\SharedContent\\media\\fish.png",".\\target\\release\\kitten_224.png"); + copy_file!("..\\..\\SharedContent\\models\\SqueezeNet.onnx",".\\target\\release\\SqueezeNet.onnx"); + copy_file!("..\\SqueezeNetObjectDetection\\Desktop\\cpp\\Labels.txt",".\\target\\release\\Labels.txt"); + } +} + +fn main() { + winrt::build!( + types + microsoft::ai::machine_learning::* + windows::graphics::imaging::* + ); + copy_resources(); +} diff --git a/Samples/RustSqueezenet/src/main.rs b/Samples/RustSqueezenet/src/main.rs new file mode 100644 index 00000000..d9799784 --- /dev/null +++ b/Samples/RustSqueezenet/src/main.rs @@ -0,0 +1,102 @@ +include!(concat!(env!("OUT_DIR"), "/winrt.rs")); + +macro_rules! handle_io_error_as_winrt_error { + ($expression:expr, $error_message:expr) => { + match $expression { + Ok(val) => val, + Err(_err) => return Err(winrt::Error::new(winrt::ErrorCode(Error::last_os_error().raw_os_error().unwrap() as u32), $error_message)), + } + } +} + +fn main() -> winrt::Result<()> { + use microsoft::ai::machine_learning::*; + use winrt::ComInterface; + + let model_path = get_current_dir()? + "\\Squeezenet.onnx"; + println!("Loading model {}", model_path); + let learning_model = LearningModel::load_from_file_path(model_path)?; + + let device = LearningModelDevice::create(LearningModelDeviceKind::Cpu)?; + + println!("Creating session"); + let session = LearningModelSession::create_from_model_on_device(learning_model, device)?; + + let image_file_path = get_current_dir()? + "\\kitten_224.png"; + println!("Loading image file {}", image_file_path); + let input_image_videoframe = load_image_file(image_file_path)?; + let input_image_feature_value = ImageFeatureValue::create_from_video_frame(input_image_videoframe)?; + let binding = LearningModelBinding::create_from_session(&session)?; + binding.bind("data_0", input_image_feature_value)?; + + println!("Evaluating"); + let results = LearningModelSession::evaluate(&session,binding, "RunId")?; + + let result_lookup = results.outputs()?.lookup("softmaxout_1")?; + let result_itensor_float : ITensorFloat = result_lookup.try_query()?; + let result_vector_view = result_itensor_float.get_as_vector_view()?; + println!("Results:"); + print_results(result_vector_view)?; + Ok(()) +} + +// Print the evaluation results. +fn print_results(results: windows::foundation::collections::IVectorView) -> winrt::Result<()> { + let labels = load_labels()?; + let mut sorted_results : std::vec::Vec<(f32,u32)> = Vec::new(); + for i in 0..results.size()? { + let result = (results.get_at(i)?, i); + sorted_results.push(result); + } + sorted_results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); + + // Display the top results + for i in 0..3 { + println!(" {} {}", labels[sorted_results[i].1 as usize], sorted_results[i].0) + } + Ok(()) +} + +// Return the path of the current directory of the executable +fn get_current_dir() -> winrt::Result { + use std::env; + use std::io::Error; + let current_exe = handle_io_error_as_winrt_error!(env::current_exe(), "Failed to get current directory of executable."); + let current_dir = current_exe.parent().unwrap(); + Ok(current_dir.display().to_string()) +} + +// Load all the SqueezeNet labeels and return in a vector of Strings. +fn load_labels() -> winrt::Result> { + use std::io::Error; + use std::fs::File; + use std::io::{prelude::*, BufReader}; + + let mut labels : std::vec::Vec = Vec::new(); + let labels_file_path = get_current_dir()? + "\\Labels.txt"; + let file = handle_io_error_as_winrt_error!(File::open(labels_file_path), "Failed to load labels."); + let reader = BufReader::new(file); + for line in reader.lines() { + let line_str = handle_io_error_as_winrt_error!(line,"Failed to read lines."); + let mut tokenized_line: Vec<&str> = line_str.split(',').collect(); + let index = tokenized_line[0].parse::().unwrap(); + labels.resize(index+1, "".to_string()); + tokenized_line.remove(0); + labels[index] = tokenized_line.join(""); + } + Ok(labels) +} + +// load image file given a path and return Videoframe +fn load_image_file(image_file_path: String) -> winrt::Result { + use windows::graphics::imaging::*; + use windows::media::*; + use windows::storage::*; + + let file = StorageFile::get_file_from_path_async(image_file_path)?.get()?; + let stream = file.open_async(FileAccessMode::Read)?.get()?; + let decoder = BitmapDecoder::create_async(&stream)?.get()?; + let software_bitmap = decoder.get_software_bitmap_async()?.get()?; + let image_videoframe = VideoFrame::create_with_software_bitmap(software_bitmap)?; + Ok(image_videoframe) +} \ No newline at end of file diff --git a/Samples/RustSqueezenet/winrt-rs b/Samples/RustSqueezenet/winrt-rs new file mode 160000 index 00000000..2edcccde --- /dev/null +++ b/Samples/RustSqueezenet/winrt-rs @@ -0,0 +1 @@ +Subproject commit 2edcccde71df2a33bd4fe5355751b8507a9d5b49