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", Cpu = "cpu",
161 CpuPinned = "cpu-pinned", 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}