use std::collections::HashMap;
use log::{error, warn};
use ort;
use crate as pipeless;
pub type OnnxInferenceOutput = HashMap<String, ndarray::ArrayBase<ndarray::OwnedRepr<f32>, ndarray::Dim<ndarray::IxDynImpl>>>;
pub struct OnnxSessionParams {
stage_name: String, execution_provider: String, execution_mode: Option<String>, inter_threads: Option<i16>, intra_threads: Option<i16>, custom_op_lib_path: Option<String>,
}
impl OnnxSessionParams {
pub fn new(
stage_name: &str,
execution_provider: &str, execution_mode: Option<&str>,
inter_threads: Option<i16>, intra_threads: Option<i16>,
custom_op_lib_path: Option<&str>,
) -> Self {
Self {
stage_name: stage_name.to_string(),
execution_provider: execution_provider.to_string(),
execution_mode: execution_mode.map(|m| m.to_string()),
inter_threads, intra_threads,
custom_op_lib_path: custom_op_lib_path.map(|p| p.to_string()),
}
}
}
pub struct OnnxSession {
session: ort::Session,
}
impl OnnxSession {
pub fn new(model_uri: &str, params: super::session::SessionParams) -> Result<Self, String> {
if let pipeless::stages::inference::session::SessionParams::Onnx(onnx_params) = params {
let execution_provider = match onnx_params.execution_provider.as_str() {
"cpu" | "CPU" => ort::ExecutionProvider::CPU(Default::default()),
"cuda" | "CUDA" => ort::ExecutionProvider::CUDA(Default::default()),
"tensorrt" | "TENSORRT" | "TensorRT" => ort::ExecutionProvider::TensorRT(Default::default()),
"openvino" | "OpenVINO" | "OpenVino" | "OPENVINO" => ort::ExecutionProvider::OpenVINO(Default::default()),
"coreml" | "CoreML" | "CoreMl" | "COREML" => ort::ExecutionProvider::CoreML(Default::default()), other => {
let err = format!("Unsupported execution provider for the ONNX Runtime: '{}'", other);
return Err(err);
}
};
let model_file_path = super::util::get_model_path(model_uri, "main")?;
let environment = ort::Environment::builder()
.with_log_level(ort::LoggingLevel::Warning)
.with_name(onnx_params.stage_name)
.with_execution_providers([execution_provider])
.build().unwrap()
.into_arc();
let mut session_builder = ort::SessionBuilder::new(&environment).unwrap();
session_builder = session_builder.with_optimization_level(ort::GraphOptimizationLevel::Level3).unwrap();
if let Some(intra_threads) = onnx_params.intra_threads {
session_builder = session_builder.with_intra_threads(intra_threads).unwrap();
}
if let Some(mode) = onnx_params.execution_mode {
match mode.as_str() {
"parallel" | "Parallel" | "PARALLEL" => {
session_builder = session_builder.with_parallel_execution(true).unwrap();
if let Some(inter_threads) = onnx_params.inter_threads {
session_builder = session_builder.with_inter_threads(inter_threads).unwrap();
}
},
"sequential" | "Sequential" | "SEQUENTIAL" => {
session_builder = session_builder.with_parallel_execution(false).unwrap();
},
mode => {
return Err(format!("Unrecognized execution mode: {}", mode));
}
}
}
if let Some(lib_path) = onnx_params.custom_op_lib_path {
log::info!("Loading custom operations lib from: {}", lib_path);
session_builder = session_builder.with_custom_op_lib(&lib_path).unwrap();
}
let session = session_builder.with_model_from_file(model_file_path).unwrap();
let input0_shape: Vec<Option<usize>> = session.inputs[0].dimensions().map(|x| x).collect();
if input0_shape.len() > 2 {
let batch_shift = if input0_shape.len() > 3 { 1 } else { 0 };
let width = input0_shape[2 + batch_shift];
let height = input0_shape[1 + batch_shift];
let channels = input0_shape[0 + batch_shift];
if let (Some(width), Some(height), Some(channels)) = (width, height, channels) {
let test_image = ndarray::Array3::<u8>::zeros((channels, height, width)).into_dyn();
let cow_array = ndarray::CowArray::from(test_image);
let ort_input_value = ort::Value::from_array(
session.allocator(),
&cow_array
).unwrap();
let _ = session.run(vec![ort_input_value]);
} else {
warn!(
"Could not run an inference test because the model input shape was not properly recognized. Obtained: width: {:?}, height: {:?}, channels: {:?}",
width.map(|num| num.to_string()).unwrap_or_else(|| "None".to_string()), height.map(|num| num.to_string()).unwrap_or_else(|| "None".to_string()),
channels.map(|num| num.to_string()).unwrap_or_else(|| "None".to_string())
);
}
} else {
warn!("Could not run an inference test because the model input shape does not contain all the image dimensions");
}
Ok(Self { session })
} else {
let err = "Wrong parameters provided to ONNX session";
Err(err.to_owned())
}
}
}
impl super::session::SessionTrait for OnnxSession {
fn infer(&self, mut frame: pipeless::data::Frame) -> pipeless::data::Frame {
let input_data = frame.get_inference_input().to_owned();
if input_data.len() == 0 {
warn!("No inference input data was provided. Did you forget to add it at your pre-process hook?");
return frame;
}
let input_vec = input_data.view().insert_axis(ndarray::Axis(0)).into_dyn(); let cow_array = ndarray::CowArray::from(input_vec);
let ort_input_value_result = ort::Value::from_array(
self.session.allocator(),
&cow_array
);
match ort_input_value_result {
Ok(ort_input_value) => {
let mut io_bindings = self.session.bind().unwrap();
let _ = io_bindings.bind_input(self.session.inputs[0].name.as_str(), ort_input_value).unwrap();
for output in &self.session.outputs {
let output_mem_info = ort::MemoryInfo::new(
ort::AllocationDevice::CPU, 0, ort::AllocatorType::Device, ort::MemType::Default
).unwrap();
let _ = io_bindings.bind_output(output.name.as_str(), output_mem_info).unwrap();
}
match self.session.run_with_binding(&io_bindings) {
Ok(()) => {
let outputs = io_bindings.outputs().unwrap();
let mut frame_inference_output = OnnxInferenceOutput::new();
for (output_name, output_value) in outputs {
match output_value.try_extract() {
Ok(output) => {
let output_ndarray = output.view().to_owned();
frame_inference_output.insert(output_name, output_ndarray);
},
Err(_err) => {
match output_value.try_extract() {
Ok(output) => {
let output_ndarray: ndarray::ArrayBase<ndarray::OwnedRepr<i64>, _> = output.view().to_owned();
let float_output = output_ndarray.mapv(|v| v as f32);
frame_inference_output.insert(output_name, float_output);
}
Err(_err) => {
match output_value.try_extract() {
Ok(output) => {
let output_ndarray: ndarray::ArrayBase<ndarray::OwnedRepr<i32>, _> = output.view().to_owned();
let float_output = output_ndarray.mapv(|v| v as f32);
frame_inference_output.insert(output_name, float_output);
}
Err(err) => warn!("Error extracting inference results: {}", err.to_string()),
}
},
}
}
}
}
frame.set_inference_output(pipeless::data::InferenceOutput::OnnxInferenceOutput(frame_inference_output));
},
Err(err) => error!("There was an error running inference: {}", err)
}
},
Err(err) => error!("There was an error creating the input tensor: {}", err)
}
frame
}
}