1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
use crate::ir::{ArgType, Argument, Node, TensorData};
use std::str::FromStr;
/// Interpolation mode for resize operation
#[derive(Debug, Clone, PartialEq)]
pub enum ResizeMode {
/// Nearest neighbor interpolation
Nearest,
/// Linear interpolation (bilinear for 2D, trilinear for 3D)
Linear,
/// Cubic interpolation
Cubic,
}
impl FromStr for ResizeMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"nearest" => Ok(ResizeMode::Nearest),
"linear" => Ok(ResizeMode::Linear),
"cubic" => Ok(ResizeMode::Cubic),
_ => Err(format!("Unsupported resize mode: {}", s)),
}
}
}
/// Configuration for the Resize operation.
#[derive(Debug, Clone)]
pub struct ResizeConfig {
pub mode: ResizeMode,
pub scales: Option<ResizeScales>,
pub sizes: Option<ResizeSizes>,
}
/// Represents either a static value or a runtime argument for resize scales.
#[derive(Debug, Clone)]
pub enum ResizeScales {
/// Static scales known at compile time.
Static(Vec<f32>),
/// Runtime scales determined during execution.
Runtime(Argument),
}
/// Represents either a static value or a runtime argument for resize sizes.
#[derive(Debug, Clone)]
pub enum ResizeSizes {
/// Static sizes known at compile time.
Static(Vec<usize>),
/// Runtime sizes determined during execution.
Runtime(Argument),
}
pub fn resize_config(node: &Node) -> ResizeConfig {
let mut mode: Option<ResizeMode> = None;
let input = if let ArgType::Tensor(tensor) = &node
.inputs
.first()
.expect("Resize: Input tensor must be present")
.ty
{
tensor
} else {
panic!("Resize: input must be a tensor")
};
// Note: we are ignoring some attributes because results are approximately the same
// and we are not supporting all the attributes of the Resize operator.
// However, some attributes are important to be checked and we are checking
// against the default values of the attributes.
// TODO revisit this when we have more Resize operators in the model
for (key, value) in node.attrs.iter() {
match key.as_str() {
"antialias" => assert_eq!(
value.clone().into_i32(),
0,
"Resize: antialias other than 0 is not supported"
),
"axes" => panic!("Resize: custom axes attribute is not supported"),
"coordinate_transformation_mode" => {
log::warn!("Resize: coordinate_transformation_mode is ignored")
}
"cubic_coeff_a" => log::warn!("Resize: cubic_coeff_a is ignored"),
"exclude_outside" => assert_eq!(
value.clone().into_i32(),
0,
"Resize: exclude_outside other than 0 is not supported"
),
"extrapolation_value" => assert_eq!(
value.clone().into_f32(),
0.0,
"Resize: extrapolation_value other than 0.0 is not supported"
),
"keep_aspect_ratio_policy" => {
assert_eq!(
value.clone().into_string().to_lowercase(),
"stretch",
"Resize: keep_aspect_ratio_policy other than 'stretch' is not supported"
)
}
"mode" => {
mode = Some(
value
.clone()
.into_string()
.parse::<ResizeMode>()
.expect("Failed to parse resize mode"),
)
}
"nearest_mode" => log::warn!("Resize: nearest_mode is ignored"),
_ => {}
}
}
let roi: Vec<f32> = node
.inputs
.get(1)
.map(|input| {
if let Some(TensorData { data, .. }) = &input.value {
data.clone().into_f32s()
} else {
vec![]
}
})
.unwrap_or_default();
// Extract scales input (3rd input)
let scales = extract_scales_input(node, input.rank);
// Extract sizes input (4th input)
let sizes = extract_sizes_input(node, input.rank);
let mode = mode.expect("Resize: mode attribute is required");
if !roi.is_empty() {
panic!("Resize: roi input is not supported")
}
// Check that at least one of scales or sizes is provided
if scales.is_none() && sizes.is_none() {
panic!("Resize: either scales or sizes input is required")
}
ResizeConfig {
mode,
scales,
sizes,
}
}
/// Extract scales input as either static or runtime
fn extract_scales_input(node: &Node, input_rank: usize) -> Option<ResizeScales> {
match node.inputs.get(2) {
Some(input) => {
// Skip empty inputs (those with empty names are placeholders)
if input.name.is_empty() {
return None;
}
match &input.ty {
ArgType::Tensor(_) => {
// Check if it's a constant tensor
match &input.value {
Some(TensorData { data, .. }) => {
let mut scales = data.clone().into_f32s();
if scales.is_empty() {
return None;
}
assert!(scales.len() == input_rank);
// ignore the first two items from scales
// because they are the batch and channel dimensions
scales = scales.iter().skip(2).cloned().collect();
Some(ResizeScales::Static(scales))
}
None => Some(ResizeScales::Runtime(input.clone())),
}
}
ArgType::Shape(_) => {
// Shape input for scales - treat as runtime
Some(ResizeScales::Runtime(input.clone()))
}
_ => None,
}
}
None => None,
}
}
/// Extract sizes input as either static or runtime
fn extract_sizes_input(node: &Node, input_rank: usize) -> Option<ResizeSizes> {
match node.inputs.get(3) {
Some(input) => {
// Skip empty inputs (those with empty names are placeholders)
if input.name.is_empty() {
return None;
}
match &input.ty {
ArgType::Tensor(_) => {
// Check if it's a constant tensor
match &input.value {
Some(TensorData { data, .. }) => {
let mut sizes: Vec<usize> = data
.clone()
.into_i64s()
.iter()
.map(|&x| x as usize)
.collect();
if sizes.is_empty() {
return None;
}
assert!(sizes.len() == input_rank);
// ignore the first two items from sizes
// because they are the batch and channel dimensions
sizes = sizes.iter().skip(2).cloned().collect();
Some(ResizeSizes::Static(sizes))
}
None => Some(ResizeSizes::Runtime(input.clone())),
}
}
ArgType::Shape(_rank) => {
// Shape input for sizes - this is the key case we're fixing
// The Shape type represents the shape of a tensor, which is exactly what we need
Some(ResizeSizes::Runtime(input.clone()))
}
_ => None,
}
}
None => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::NodeType;
use crate::node::test_utils::NodeBuilder;
fn create_test_node(
mode: &str,
scales: Option<Vec<f32>>,
sizes: Option<Vec<i64>>,
roi: Option<Vec<f32>>,
) -> Node {
let mut builder = NodeBuilder::new(NodeType::Resize, "test_resize")
.input_tensor_f32("X", 4, None) // N,C,H,W format
.output_tensor_f32("Y", 4, None)
.attr_string("mode", mode);
// Add ROI input if provided
if let Some(roi_data) = roi {
builder = builder.input_tensor_f32_data("roi", roi_data.clone(), vec![8]);
// For 4D input (start x, start y, end x, end y)
} else {
// Empty ROI still needs to be added as a placeholder with empty name
builder = builder.input_tensor_f32("", 1, None);
}
// Add scales input if provided
if let Some(scales_data) = scales {
builder = builder.input_tensor_f32_data("scales", scales_data.clone(), vec![4]);
// N,C,H,W scales
} else {
// Empty scales still needs to be added as a placeholder with empty name
builder = builder.input_tensor_f32("", 1, None);
}
// Add sizes input if provided
if let Some(sizes_data) = sizes {
builder = builder.input_tensor_i64_data("sizes", sizes_data.clone(), vec![4]);
// N,C,H,W sizes
} else {
// Empty sizes still needs to be added as a placeholder with empty name
builder = builder.input_tensor_i64("", 1, None);
}
builder.build()
}
#[test]
fn test_resize_config_with_scales() {
let node = create_test_node(
"nearest",
Some(vec![1.0, 1.0, 2.0, 2.0]), // Keep N,C same, double H,W
None,
None,
);
let config = resize_config(&node);
assert_eq!(config.mode, ResizeMode::Nearest);
match config.scales {
Some(ResizeScales::Static(scales)) => {
assert_eq!(scales, vec![2.0, 2.0]); // Only the spatial scales (H,W)
}
_ => panic!("Expected static scales"),
}
assert!(config.sizes.is_none(), "Expected no sizes");
}
#[test]
fn test_resize_config_with_sizes() {
let node = create_test_node(
"linear",
None,
Some(vec![1, 3, 224, 224]), // Fixed output size
None,
);
let config = resize_config(&node);
assert_eq!(config.mode, ResizeMode::Linear);
assert!(config.scales.is_none(), "Expected no scales");
match config.sizes {
Some(ResizeSizes::Static(sizes)) => {
assert_eq!(sizes, vec![224, 224]); // Only the spatial sizes (H,W)
}
_ => panic!("Expected static sizes"),
}
}
#[test]
#[should_panic(expected = "Resize: roi input is not supported")]
fn test_resize_config_with_roi() {
let node = create_test_node(
"nearest",
Some(vec![1.0, 1.0, 2.0, 2.0]),
None,
Some(vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), // ROI values
);
let _ = resize_config(&node);
}
#[test]
#[should_panic(expected = "Resize: either scales or sizes input is required")]
fn test_resize_config_no_scales_or_sizes() {
let node = create_test_node("nearest", None, None, None);
let _ = resize_config(&node);
}
#[test]
#[should_panic(expected = "Resize: mode attribute is required")]
fn test_resize_config_no_mode() {
let mut node = create_test_node("nearest", Some(vec![1.0, 1.0, 2.0, 2.0]), None, None);
node.attrs.clear(); // Remove all attributes including mode
let _ = resize_config(&node);
}
}