Skip to main content

ort_web/binding/
tensor.rs

1use alloc::{string::ToString, vec::Vec};
2
3use js_sys::JsString;
4use serde::{Deserialize, Serialize};
5use wasm_bindgen::prelude::*;
6use web_sys::{HtmlImageElement, ImageBitmap, ImageData, WebGlTexture};
7
8use crate::binding::DataType;
9
10#[derive(Serialize, Debug, Clone, Copy)]
11#[serde(rename_all = "UPPERCASE")]
12pub enum ImageFormat {
13	Rgb,
14	Rgba,
15	Bgr,
16	Rbg
17}
18
19#[derive(Serialize, Debug, Clone, Copy)]
20#[serde(rename_all = "UPPERCASE")]
21pub enum ImageTensorLayout {
22	Nhwc,
23	Nchw
24}
25
26#[derive(Serialize, Debug, Clone, Copy)]
27#[serde(rename_all = "lowercase")]
28pub enum ImageDataType {
29	Float32,
30	Uint8
31}
32
33impl Into<DataType> for ImageDataType {
34	fn into(self) -> DataType {
35		match self {
36			Self::Float32 => DataType::Float32,
37			Self::Uint8 => DataType::Uint8
38		}
39	}
40}
41
42#[derive(Serialize)]
43#[serde(untagged)]
44pub enum ImageNormOption {
45	Splat(f32),
46	PerChannel([f32; 3]),
47	PerChannelWithAlpha([f32; 4])
48}
49
50#[derive(Serialize, Default)]
51#[serde(rename_all = "camelCase")]
52pub struct ImageNorm {
53	pub bias: Option<ImageNormOption>,
54	pub mean: Option<ImageNormOption>
55}
56
57impl ImageNorm {
58	pub const fn imagenet(format: ImageFormat) -> ImageNorm {
59		const RGB_MEAN: [f32; 3] = [0.485, 0.456, 0.406];
60		const RGB_STD: [f32; 3] = [0.229, 0.224, 0.225];
61		ImageNorm {
62			mean: Some(match format {
63				ImageFormat::Rgb => ImageNormOption::PerChannel(RGB_MEAN),
64				ImageFormat::Rgba => ImageNormOption::PerChannelWithAlpha([RGB_MEAN[0], RGB_MEAN[1], RGB_MEAN[2], 0.5]),
65				ImageFormat::Bgr => ImageNormOption::PerChannel([RGB_MEAN[2], RGB_MEAN[1], RGB_MEAN[0]]),
66				ImageFormat::Rbg => ImageNormOption::PerChannel([RGB_MEAN[0], RGB_MEAN[2], RGB_MEAN[1]])
67			}),
68			bias: Some(match format {
69				ImageFormat::Rgb => ImageNormOption::PerChannel(RGB_STD),
70				ImageFormat::Rgba => ImageNormOption::PerChannelWithAlpha([RGB_STD[0], RGB_STD[1], RGB_STD[2], 0.5]),
71				ImageFormat::Bgr => ImageNormOption::PerChannel([RGB_STD[2], RGB_STD[1], RGB_STD[0]]),
72				ImageFormat::Rbg => ImageNormOption::PerChannel([RGB_STD[0], RGB_STD[2], RGB_STD[1]])
73			})
74		}
75	}
76}
77
78#[derive(Serialize, Default)]
79#[serde(rename_all = "camelCase")]
80pub struct TensorFromImageOptions {
81	pub data_type: Option<ImageDataType>,
82	pub norm: Option<ImageNorm>,
83	pub resized_height: Option<u32>,
84	pub resized_width: Option<u32>,
85	pub tensor_format: Option<ImageFormat>,
86	pub tensor_layout: Option<ImageTensorLayout>
87}
88
89impl TensorFromImageOptions {
90	pub(crate) fn to_value(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
91		serde_wasm_bindgen::to_value(self)
92	}
93}
94
95#[derive(Serialize, Default)]
96#[serde(rename_all = "camelCase")]
97pub struct TensorFromUrlOptions {
98	#[serde(flatten)]
99	pub base: TensorFromImageOptions,
100	pub width: Option<u32>,
101	pub height: Option<u32>
102}
103
104impl TensorFromUrlOptions {
105	pub(crate) fn to_value(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
106		serde_wasm_bindgen::to_value(self)
107	}
108}
109
110#[derive(Serialize)]
111#[serde(transparent)]
112pub struct DisposeFunction(#[serde(with = "serde_wasm_bindgen::preserve")] JsValue);
113
114impl<T> From<T> for DisposeFunction
115where
116	T: FnOnce() + 'static
117{
118	fn from(value: T) -> Self {
119		DisposeFunction(Closure::once_into_js(value))
120	}
121}
122
123#[derive(Serialize)]
124#[serde(transparent)]
125pub struct DownloadFunction(#[serde(with = "serde_wasm_bindgen::preserve")] JsValue);
126
127impl<T, F, E> From<T> for DownloadFunction
128where
129	T: FnOnce() -> F + 'static,
130	F: Future<Output = Result<JsValue, E>> + 'static,
131	E: core::error::Error
132{
133	fn from(value: T) -> Self {
134		DownloadFunction(Closure::once_into_js(move || {
135			wasm_bindgen_futures::future_to_promise(async move {
136				match value().await {
137					Ok(value) => Ok(value),
138					Err(e) => Err(JsString::from(e.to_string()).into())
139				}
140			})
141		}))
142	}
143}
144
145#[derive(Serialize)]
146#[serde(rename_all = "camelCase")]
147pub struct TensorFromTextureOptions {
148	pub width: u32,
149	pub height: u32,
150	pub format: Option<ImageFormat>,
151	pub dispose: Option<DisposeFunction>,
152	pub download: Option<DownloadFunction>
153}
154
155#[wasm_bindgen]
156#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
157#[serde(rename_all = "kebab-case")]
158pub enum DataLocation {
159	None = "none", // indicates tensor is disposed
160	Cpu = "cpu",
161	CpuPinned = "cpu-pinned", // what is *pinned* in WASM?
162	Texture = "texture",
163	GpuBuffer = "gpu-buffer",
164	MlTensor = "ml-tensor"
165}
166
167#[wasm_bindgen]
168extern "C" {
169	#[wasm_bindgen(js_namespace = ort)]
170	pub type Tensor;
171
172	#[wasm_bindgen(catch, js_namespace = ort, static_method_of = Tensor, js_name = fromImage)]
173	async fn from_image_data_raw(image_data: &ImageData, options: JsValue) -> Result<Tensor, JsValue>;
174	#[wasm_bindgen(catch, js_namespace = ort, static_method_of = Tensor, js_name = fromImage)]
175	async fn from_image_element_raw(element: &HtmlImageElement, options: JsValue) -> Result<Tensor, JsValue>;
176	#[wasm_bindgen(catch, js_namespace = ort, static_method_of = Tensor, js_name = fromImage)]
177	async fn from_image_bitmap_raw(bitmap: &ImageBitmap, options: JsValue) -> Result<Tensor, JsValue>;
178	#[wasm_bindgen(catch, js_namespace = ort, static_method_of = Tensor, js_name = fromImage)]
179	async fn from_image_url_raw(url: &str, options: JsValue) -> Result<Tensor, JsValue>;
180	#[wasm_bindgen(catch, js_namespace = ort, static_method_of = Tensor, js_name = fromTexture)]
181	fn from_texture(texture: &WebGlTexture, options: JsValue) -> Result<Tensor, JsValue>;
182	#[cfg(web_sys_unstable_apis)]
183	#[wasm_bindgen(catch, js_namespace = ort, static_method_of = Tensor, js_name = fromGpuBuffer)]
184	fn from_gpu_buffer(buffer: &web_sys::GpuBuffer, options: JsValue) -> Result<Tensor, JsValue>;
185	#[wasm_bindgen(catch, js_namespace = ort, static_method_of = Tensor, js_name = fromPinnedBuffer)]
186	fn from_pinned_buffer(dtype: DataType, buffer: JsValue, dims: JsValue) -> Result<Tensor, JsValue>;
187
188	#[wasm_bindgen(constructor, catch, js_namespace = ort, js_class = Tensor)]
189	fn new_from_buffer_raw(dtype: DataType, buffer: JsValue, dims: JsValue) -> Result<Tensor, JsValue>;
190
191	#[wasm_bindgen(structural, catch, method, getter, js_name = data)]
192	pub fn data(this: &Tensor) -> Result<JsValue, JsValue>;
193	#[wasm_bindgen(structural, method, getter, js_name = location)]
194	pub fn location(this: &Tensor) -> DataLocation;
195	#[wasm_bindgen(structural, method, getter, js_name = type)]
196	pub fn dtype(this: &Tensor) -> DataType;
197	#[wasm_bindgen(structural, method, getter, js_name = size)]
198	pub fn size(this: &Tensor) -> usize;
199	#[wasm_bindgen(structural, method, getter, js_name = dims)]
200	pub fn dims(this: &Tensor) -> Vec<i32>;
201
202	#[wasm_bindgen(structural, catch, method, js_name = getData)]
203	pub async fn get_data(this: &Tensor) -> Result<JsValue, JsValue>;
204
205	#[wasm_bindgen(structural, catch, method, js_name = dispose)]
206	pub fn dispose(this: &Tensor) -> Result<(), JsValue>;
207	#[wasm_bindgen(structural, catch, method, js_name = reshape)]
208	fn reshape(this: &Tensor, dims: JsValue) -> Result<Tensor, JsValue>;
209}
210
211impl Tensor {
212	pub async fn from_image_data(image_data: &ImageData, options: &TensorFromImageOptions) -> Result<Tensor, JsValue> {
213		Self::from_image_data_raw(image_data, options.to_value()?).await
214	}
215
216	pub async fn from_image_element(element: &HtmlImageElement, options: &TensorFromImageOptions) -> Result<Tensor, JsValue> {
217		Self::from_image_element_raw(element, options.to_value()?).await
218	}
219
220	pub async fn from_image_bitmap(bitmap: &ImageBitmap, options: &TensorFromImageOptions) -> Result<Tensor, JsValue> {
221		Self::from_image_bitmap_raw(bitmap, options.to_value()?).await
222	}
223
224	pub async fn from_image_url(url: &str, options: &TensorFromUrlOptions) -> Result<Tensor, JsValue> {
225		Self::from_image_url_raw(url, options.to_value()?).await
226	}
227
228	pub fn new_from_buffer(dtype: DataType, buffer: JsValue, dims: &[i32]) -> Result<Tensor, JsValue> {
229		Self::new_from_buffer_raw(dtype, buffer, convert_dims(dims))
230	}
231}
232
233fn convert_dims(dims: &[i32]) -> JsValue {
234	dims.iter().map(|d| js_sys::Number::from(*d)).collect::<js_sys::Array>().into()
235}