1use crate::transforms::Transform;
16use torsh_core::error::Result;
17use torsh_core::{
18 dtype::{FloatElement, TensorElement},
19 error::TorshError,
20};
21use torsh_tensor::Tensor;
22use scirs2_core::random::thread_rng;
24
25#[derive(Debug, Clone)]
30pub struct RandomHorizontalFlip {
31 prob: f32,
32}
33
34impl RandomHorizontalFlip {
35 pub fn new(prob: f32) -> Self {
43 assert!(
44 (0.0..=1.0).contains(&prob),
45 "Probability must be between 0 and 1"
46 );
47 Self { prob }
48 }
49}
50
51impl<T: FloatElement> Transform<Tensor<T>> for RandomHorizontalFlip {
52 type Output = Tensor<T>;
53
54 fn transform(&self, input: Tensor<T>) -> Result<Self::Output> {
55 let mut rng = thread_rng(); let random_val = rng.random::<f32>();
58 if random_val < self.prob {
59 self.horizontal_flip(input)
60 } else {
61 Ok(input)
62 }
63 }
64
65 fn is_deterministic(&self) -> bool {
66 false
67 }
68}
69
70impl RandomHorizontalFlip {
71 fn horizontal_flip<T: FloatElement>(&self, input: Tensor<T>) -> Result<Tensor<T>> {
72 let binding = input.shape();
73 let shape = binding.dims();
74 if shape.len() < 2 {
75 return Err(TorshError::InvalidArgument(
76 "Input tensor must have at least 2 dimensions for horizontal flip".to_string(),
77 ));
78 }
79 let device = input.device();
80 let (height, width, channels) = if shape.len() == 2 {
82 (shape[0], shape[1], 1usize)
83 } else {
84 (
85 shape[shape.len() - 2],
86 shape[shape.len() - 1],
87 shape[..shape.len() - 2].iter().product(),
88 )
89 };
90 let mut data = input
91 .to_vec()
92 .map_err(|e| TorshError::Other(format!("to_vec failed: {}", e)))?;
93 for c in 0..channels {
95 for row in 0..height {
96 for col in 0..width / 2 {
97 let mirror_col = width - 1 - col;
98 let idx1 = c * height * width + row * width + col;
99 let idx2 = c * height * width + row * width + mirror_col;
100 data.swap(idx1, idx2);
101 }
102 }
103 }
104 Tensor::from_data(data, shape.to_vec(), device).map_err(|e| e)
105 }
106}
107
108#[derive(Debug, Clone)]
113pub struct RandomCrop {
114 size: (usize, usize),
115 padding: Option<usize>,
116}
117
118impl RandomCrop {
119 pub fn new(size: (usize, usize)) -> Self {
124 Self {
125 size,
126 padding: None,
127 }
128 }
129
130 pub fn with_padding(mut self, padding: usize) -> Self {
135 self.padding = Some(padding);
136 self
137 }
138}
139
140impl<T: TensorElement> Transform<Tensor<T>> for RandomCrop {
141 type Output = Tensor<T>;
142
143 fn transform(&self, input: Tensor<T>) -> Result<Self::Output> {
144 let shape = input.shape();
145 let dims = shape.dims();
146
147 if dims.len() < 2 {
149 return Err(TorshError::InvalidArgument(
150 "Input tensor must have at least 2 dimensions for random crop".to_string(),
151 ));
152 }
153
154 let (input_height, input_width) = if dims.len() == 2 {
155 (dims[0], dims[1])
156 } else {
157 (dims[1], dims[2])
159 };
160
161 let (crop_height, crop_width) = self.size;
162
163 if crop_height > input_height || crop_width > input_width {
165 if let Some(padding) = self.padding {
166 let _new_height = input_height.max(crop_height) + 2 * padding;
168 let _new_width = input_width.max(crop_width) + 2 * padding;
169
170 return Ok(input);
174 } else {
175 return Err(TorshError::InvalidArgument(
176 format!("Crop size ({crop_height}, {crop_width}) is larger than input size ({input_height}, {input_width}) and no padding specified"),
177 ));
178 }
179 }
180
181 let mut rng = thread_rng();
183 let max_y = input_height - crop_height;
184 let max_x = input_width - crop_width;
185
186 let _start_y = if max_y > 0 {
187 rng.gen_range(0..=max_y)
188 } else {
189 0
190 };
191 let _start_x = if max_x > 0 {
192 rng.gen_range(0..=max_x)
193 } else {
194 0
195 };
196
197 Ok(input)
207 }
208
209 fn is_deterministic(&self) -> bool {
210 false
211 }
212}
213
214#[derive(Clone, Copy, Debug, PartialEq, Eq)]
216pub enum InterpolationMode {
217 Nearest,
219 Linear,
221 Bilinear,
223 Bicubic,
225}
226
227impl Default for InterpolationMode {
228 fn default() -> Self {
229 Self::Bilinear
230 }
231}
232
233#[derive(Debug, Clone)]
238pub struct Resize {
239 size: (usize, usize),
240 interpolation: InterpolationMode,
241}
242
243impl Resize {
244 pub fn new(size: (usize, usize)) -> Self {
249 Self {
250 size,
251 interpolation: InterpolationMode::Bilinear,
252 }
253 }
254
255 pub fn with_interpolation(mut self, mode: InterpolationMode) -> Self {
260 self.interpolation = mode;
261 self
262 }
263}
264
265impl<T: FloatElement> Transform<Tensor<T>> for Resize {
266 type Output = Tensor<T>;
267
268 fn transform(&self, input: Tensor<T>) -> Result<Self::Output> {
269 let shape = input.shape();
270 let dims = shape.dims();
271
272 if dims.len() < 2 {
274 return Err(TorshError::InvalidArgument(
275 "Input tensor must have at least 2 dimensions for resize".to_string(),
276 ));
277 }
278
279 let (input_height, input_width) = if dims.len() == 2 {
280 (dims[0], dims[1])
281 } else {
282 (dims[1], dims[2])
284 };
285
286 let (target_height, target_width) = self.size;
287
288 if input_height == target_height && input_width == target_width {
290 return Ok(input);
291 }
292
293 match self.interpolation {
308 InterpolationMode::Nearest => {
309 Ok(input)
312 }
313 InterpolationMode::Linear | InterpolationMode::Bilinear => {
314 Ok(input)
317 }
318 InterpolationMode::Bicubic => {
319 Ok(input)
322 }
323 }
324 }
325
326 fn is_deterministic(&self) -> bool {
327 true
328 }
329}
330
331#[derive(Debug, Clone)]
336pub struct CenterCrop {
337 size: (usize, usize),
338}
339
340impl CenterCrop {
341 pub fn new(size: (usize, usize)) -> Self {
346 Self { size }
347 }
348}
349
350impl<T: TensorElement> Transform<Tensor<T>> for CenterCrop {
351 type Output = Tensor<T>;
352
353 fn transform(&self, input: Tensor<T>) -> Result<Self::Output> {
354 let shape = input.shape();
355 let dims = shape.dims();
356
357 if dims.len() < 2 {
359 return Err(TorshError::InvalidArgument(
360 "Input tensor must have at least 2 dimensions for center crop".to_string(),
361 ));
362 }
363
364 let (input_height, input_width) = if dims.len() == 2 {
365 (dims[0], dims[1])
366 } else {
367 (dims[1], dims[2])
369 };
370
371 let (crop_height, crop_width) = self.size;
372
373 if crop_height > input_height || crop_width > input_width {
375 return Err(TorshError::InvalidArgument(
376 format!("Crop size ({crop_height}, {crop_width}) is larger than input size ({input_height}, {input_width})"),
377 ));
378 }
379
380 let _start_y = (input_height - crop_height) / 2;
382 let _start_x = (input_width - crop_width) / 2;
383
384 Ok(input)
400 }
401
402 fn is_deterministic(&self) -> bool {
403 true
404 }
405}
406
407pub fn random_horizontal_flip(prob: f32) -> RandomHorizontalFlip {
409 RandomHorizontalFlip::new(prob)
410}
411
412pub fn random_crop(size: (usize, usize)) -> RandomCrop {
414 RandomCrop::new(size)
415}
416
417pub fn resize(size: (usize, usize)) -> Resize {
419 Resize::new(size)
420}
421
422pub fn center_crop(size: (usize, usize)) -> CenterCrop {
424 CenterCrop::new(size)
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use torsh_core::device::DeviceType;
431
432 fn mock_tensor_2d() -> Tensor<f32> {
433 Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![2, 2], DeviceType::Cpu).unwrap()
434 }
435
436 fn mock_tensor_3d() -> Tensor<f32> {
437 Tensor::from_data(
438 vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
439 vec![2, 2, 2], DeviceType::Cpu,
441 )
442 .unwrap()
443 }
444
445 #[test]
446 fn test_random_horizontal_flip_creation() {
447 let flip = RandomHorizontalFlip::new(0.5);
448 let _test: &dyn Transform<Tensor<f32>, Output = Tensor<f32>> = &flip;
449 assert!(!_test.is_deterministic());
450 }
451
452 #[test]
453 #[should_panic]
454 fn test_random_horizontal_flip_invalid_prob() {
455 RandomHorizontalFlip::new(1.5); }
457
458 #[test]
459 fn test_random_crop_creation() {
460 let crop = RandomCrop::new((224, 224));
461 let _test: &dyn Transform<Tensor<f32>, Output = Tensor<f32>> = &crop;
462 assert!(!_test.is_deterministic());
463 }
464
465 #[test]
466 fn test_random_crop_with_padding() {
467 let crop = RandomCrop::new((224, 224)).with_padding(10);
468 let _test: &dyn Transform<Tensor<f32>, Output = Tensor<f32>> = &crop;
469 assert!(!_test.is_deterministic());
470 }
471
472 #[test]
473 fn test_resize_creation() {
474 let resize_transform = Resize::new((224, 224));
475 let _test: &dyn Transform<Tensor<f32>, Output = Tensor<f32>> = &resize_transform;
476 assert!(_test.is_deterministic());
477 }
478
479 #[test]
480 fn test_resize_with_interpolation() {
481 let resize_transform =
482 Resize::new((224, 224)).with_interpolation(InterpolationMode::Nearest);
483 let _test: &dyn Transform<Tensor<f32>, Output = Tensor<f32>> = &resize_transform;
484 assert!(_test.is_deterministic());
485 }
486
487 #[test]
488 fn test_center_crop_creation() {
489 let crop = CenterCrop::new((224, 224));
490 let _test: &dyn Transform<Tensor<f32>, Output = Tensor<f32>> = &crop;
491 assert!(_test.is_deterministic());
492 }
493
494 #[test]
495 fn test_interpolation_mode_default() {
496 assert_eq!(InterpolationMode::default(), InterpolationMode::Bilinear);
497 }
498
499 #[test]
500 fn test_tensor_transforms_2d() {
501 let tensor = mock_tensor_2d();
502
503 let flip = RandomHorizontalFlip::new(1.0); let result = flip.transform(tensor.clone());
505 assert!(result.is_ok());
506
507 let crop = CenterCrop::new((1, 1));
508 let result = crop.transform(tensor.clone());
509 assert!(result.is_ok());
510
511 let resize_transform = Resize::new((4, 4));
512 let result = resize_transform.transform(tensor);
513 assert!(result.is_ok());
514 }
515
516 #[test]
517 fn test_tensor_transforms_3d() {
518 let tensor = mock_tensor_3d();
519
520 let flip = RandomHorizontalFlip::new(0.0); let result = flip.transform(tensor.clone());
522 assert!(result.is_ok());
523
524 let crop = CenterCrop::new((1, 1));
525 let result = crop.transform(tensor.clone());
526 assert!(result.is_ok());
527
528 let resize_transform = Resize::new((4, 4));
529 let result = resize_transform.transform(tensor);
530 assert!(result.is_ok());
531 }
532
533 #[test]
534 fn test_convenience_functions() {
535 let _flip = random_horizontal_flip(0.5);
536 let _crop = random_crop((224, 224));
537 let _resize = resize((256, 256));
538 let _center = center_crop((224, 224));
539 }
540
541 #[test]
542 fn test_invalid_tensor_dimensions() {
543 let tensor_1d = Tensor::from_data(vec![1.0f32, 2.0], vec![2], DeviceType::Cpu).unwrap();
544
545 let flip = RandomHorizontalFlip::new(1.0);
546 assert!(flip.transform(tensor_1d.clone()).is_err());
547
548 let crop = CenterCrop::new((1, 1));
549 assert!(crop.transform(tensor_1d.clone()).is_err());
550
551 let resize_transform = Resize::new((4, 4));
552 assert!(resize_transform.transform(tensor_1d).is_err());
553 }
554
555 #[test]
556 fn test_crop_size_validation() {
557 let tensor = mock_tensor_2d(); let crop = CenterCrop::new((3, 3)); assert!(crop.transform(tensor.clone()).is_err());
561
562 let random_crop = RandomCrop::new((3, 3)); assert!(random_crop.transform(tensor).is_err());
564 }
565
566 #[test]
567 fn test_horizontal_flip_changes_tensor() {
568 use torsh_core::device::DeviceType;
569 let flip = RandomHorizontalFlip::new(1.0); let tensor = Tensor::from_data(
572 vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0],
573 vec![2, 3],
574 DeviceType::Cpu,
575 )
576 .unwrap();
577 let result = flip.transform(tensor).unwrap();
578 let data = result.to_vec().unwrap();
579 assert!(
580 (data[0] - 3.0).abs() < 1e-6,
581 "First element should be 3.0 after horizontal flip, got {}",
582 data[0]
583 );
584 assert!(
585 (data[2] - 1.0).abs() < 1e-6,
586 "Third element should be 1.0 after horizontal flip, got {}",
587 data[2]
588 );
589 }
590}