Skip to content

Commit 88ad43e

Browse files
committed
More error types
1 parent a265e04 commit 88ad43e

File tree

7 files changed

+190
-79
lines changed

7 files changed

+190
-79
lines changed

src/backgrounds/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@ use std::fs::DirEntry;
33
use std::path::Path;
44
use std::sync::Mutex;
55
use std::time::Instant;
6-
use anyhow::{anyhow, Result};
76
use chrono::{DateTime, Local};
87
use image::{DynamicImage, RgbaImage};
98
use log::{debug, trace, warn};
109
use rand::seq::SliceRandom;
1110
use rand::{Rng, thread_rng};
1211
use rayon::iter::ParallelBridge;
1312
use rayon::iter::ParallelIterator;
13+
use crate::generator::error::GenerationError;
1414

1515
pub struct BackgroundLoader {
1616
pub backgrounds: Vec<BackgroundImage>,
1717
}
1818

1919
impl BackgroundLoader {
20-
pub fn new<Q: AsRef<Path>>(path: Q) -> Result<BackgroundLoader> {
20+
pub fn new<Q: AsRef<Path>>(path: Q) -> Result<BackgroundLoader, GenerationError> {
2121
let dir = path.as_ref().to_path_buf();
2222
let mut v = vec![]; // mutex for multi-thread access
2323

2424
let start = Instant::now();
2525

2626
if !dir.is_dir() {
27-
return Err(anyhow!("Path provided is not a directory!"));
27+
return Err(GenerationError::NotADirectory);
2828
}
2929

3030
debug!("Loading backgrounds from: {:?}", dir);

src/generator/coco/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl CocoGenerator {
5959
self.image_id
6060
}
6161

62-
pub fn add_annotation(&mut self, image_id: u32, category_id: u32, iscrowd: u8, segmentation: Vec<Vec<f32>>, area: f64, bbox: BoundingBox) {
62+
pub fn add_annotation(&mut self, image_id: u32, category_id: u32, iscrowd: u8, segmentation: Vec<Vec<f32>>, area: f64, bbox: BoundingBox) -> u32 {
6363
self.file.annotations.push(CocoAnnotation {
6464
id: Some(self.annotation_id),
6565
image_id,
@@ -70,6 +70,8 @@ impl CocoGenerator {
7070
bbox,
7171
});
7272
self.annotation_id += 1;
73+
74+
self.annotation_id
7375
}
7476
}
7577

src/generator/config.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use image::Rgba;
2+
3+
#[derive(Debug, Clone, PartialEq)]
4+
pub struct TargetGeneratorConfig {
5+
pub visualize_bboxes: bool,
6+
pub maskover_color: Option<Rgba<u8>>
7+
}
8+
9+
impl Default for TargetGeneratorConfig {
10+
fn default() -> Self {
11+
Self {
12+
visualize_bboxes: false,
13+
maskover_color: None
14+
}
15+
}
16+
}

src/generator/error.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use thiserror::Error;
2+
3+
#[derive(Debug, Error)]
4+
pub enum GenerationError {
5+
#[error("Calculated new sizes provided an invalid size")]
6+
SizeError,
7+
#[error("No objects were provided to generate")]
8+
NoObjects,
9+
#[error("Path provided is not a directory")]
10+
NotADirectory,
11+
#[error("Missing objects.json file")]
12+
MissingObjectsJSON,
13+
#[error("{0}")]
14+
GenericError(String),
15+
#[error("Not enough objects available to generate")]
16+
NotEnoughObjectsAvailable,
17+
18+
// conversions
19+
#[error("Error parsing integer")]
20+
ParseIntError(#[from] std::num::ParseIntError),
21+
#[error("Error parsing image")]
22+
ImageError(#[from] image::ImageError),
23+
#[error("Serde decoding or encoding error")]
24+
SerdeError(#[from] serde_json::Error),
25+
#[error("IO error occurred while generating target")]
26+
IOError(#[from] std::io::Error),
27+
}

src/generator/mod.rs

Lines changed: 44 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,37 @@
11
use std::ops::RangeTo;
22
use std::path::{Path, PathBuf};
33
use std::time::Instant;
4-
use anyhow::Result;
54
use image::{GenericImage, ImageBuffer, Rgba, RgbaImage};
65
use image::imageops::FilterType;
76
use imageproc::drawing::draw_text_mut;
87
use log::{debug, trace};
9-
use rand::{Rng, thread_rng};
8+
use rand::{thread_rng, Rng};
109
use simple_logger::SimpleLogger;
1110
use strum::Display;
1211
use thiserror::Error;
12+
use error::GenerationError;
13+
use util::STANDARD_PPM;
1314
use crate::backgrounds::BackgroundLoader;
1415
use crate::generator::coco::{CocoCategoryInfo, CocoGenerator};
15-
use crate::objects::{ObjectClass, ObjectManager};
16+
use crate::generator::config::TargetGeneratorConfig;
17+
use crate::objects::{ObjectClass, ObjectManager, PlacedObject};
1618

1719
pub mod coco;
20+
pub mod error;
21+
pub(crate) mod util;
22+
pub mod config;
1823

1924
pub struct TargetGenerator {
2025
output: PathBuf,
2126
backgrounds_path: PathBuf,
2227
pub object_manager: ObjectManager,
2328
background_loader: BackgroundLoader,
24-
coco_generator: CocoGenerator
29+
coco_generator: CocoGenerator,
30+
config: TargetGeneratorConfig,
2531
}
2632

2733
impl TargetGenerator {
28-
pub fn new<Q: AsRef<Path>>(output: Q, background_path: Q, objects_path: Q, annotations_path: Q) -> Result<Self> {
34+
pub fn new<Q: AsRef<Path>>(output: Q, background_path: Q, objects_path: Q, annotations_path: Q) -> Result<Self, GenerationError> {
2935

3036
let mut object_manager = ObjectManager::new(objects_path);
3137
object_manager.load_objects()?;
@@ -35,51 +41,67 @@ impl TargetGenerator {
3541
backgrounds_path: background_path.as_ref().to_path_buf(),
3642
object_manager,
3743
background_loader: BackgroundLoader::new(background_path)?,
38-
coco_generator: CocoGenerator::new(annotations_path, ObjectClass::categories())
44+
coco_generator: CocoGenerator::new(annotations_path, ObjectClass::categories()),
45+
config: TargetGeneratorConfig::default(),
3946
})
4047
}
4148

42-
pub fn generate_target(&mut self, pixels_per_meter: f32) -> Result<RgbaImage> {
49+
pub fn generate_target(&mut self, pixels_per_meter: f32, number_of_objects: u16) -> Result<RgbaImage, GenerationError> {
4350
trace!("Beginning to generate a target...");
51+
52+
if number_of_objects == 0 {
53+
return Err(GenerationError::NoObjects);
54+
}
4455

4556
let background = self.background_loader.random().unwrap();
4657
let mut image = background.image.clone();
4758
let (w, h) = (image.width(), image.height());
48-
let set = self.object_manager.generate_set(1)?;
59+
let set = self.object_manager.generate_set(number_of_objects as u32)?;
60+
let mut placed_objects = vec![];
4961

5062
// add background image to coco here
51-
self.coco_generator.add_image(w, h, background.filename.clone(), background.date_captured.clone());
63+
let background_id = self.coco_generator.add_image(w, h, background.filename.clone(), background.date_captured.clone());
5264

5365
for obj in set {
5466
let clone = &obj.dynamic_image.clone();
5567
let (obj_w, obj_h) = (obj.dynamic_image.width(), obj.dynamic_image.height());
5668
let (x, y) = (thread_rng().gen_range(0..w - obj_w), thread_rng().gen_range(0..h - obj_h));
5769
trace!("Placing object at {}, {}", x, y);
5870

59-
let (obj_w, obj_h) = new_sizes(obj_w, obj_h, pixels_per_meter, obj.object_width_meters)?;
71+
let (obj_w, obj_h) = util::new_sizes(obj_w, obj_h, pixels_per_meter, obj.object_width_meters)?;
6072
debug!("Resizing object to {}x{}", obj_w, obj_h);
6173

6274
// overlay respects transparent pixels unlike copy_from
6375
image::imageops::overlay(&mut image, &clone.resize(obj_w, obj_h, FilterType::Gaussian), x as i64, y as i64);
6476

65-
// TODO: remove, both are top left
66-
//imageproc::drawing::draw_filled_circle_mut(&mut image, (x as i32, y as i32), 4, Rgba([255, 0, 0, 255]));
67-
//imageproc::drawing::draw_filled_circle_mut(&mut image, (0i32, 0i32), 8, Rgba([255, 0, 255, 255]));
68-
imageproc::drawing::draw_hollow_rect_mut(&mut image, imageproc::rect::Rect::at(x as i32, y as i32).of_size(obj_w, obj_h), Rgba([0, 255, 0, 255]));
77+
if self.config.visualize_bboxes {
78+
imageproc::drawing::draw_hollow_rect_mut(&mut image, imageproc::rect::Rect::at(x as i32, y as i32).of_size(obj_w, obj_h), Rgba([0, 255, 0, 255]));
79+
}
6980

70-
// add annotation to coco here
71-
self.coco_generator.add_annotation(0, obj.object_class as u32, 0, vec![], (obj_w * obj_h) as f64, coco::BoundingBox {
81+
if let Some(color) = self.config.maskover_color {
82+
imageproc::drawing::draw_filled_rect_mut(&mut image, imageproc::rect::Rect::at(x as i32, y as i32).of_size(obj_w, obj_h), color);
83+
}
84+
85+
let bbox = coco::BoundingBox {
7286
x,
7387
y,
7488
width: obj_w,
7589
height: obj_h,
90+
};
91+
92+
// add annotation to coco here
93+
let object_id = self.coco_generator.add_annotation(background_id, obj.object_class as u32, 0, vec![], (obj_w * obj_h) as f64, bbox);
94+
95+
placed_objects.push(PlacedObject {
96+
id: object_id,
97+
bounding_box: bbox,
7698
});
7799
}
78100

79101
Ok(image)
80102
}
81103

82-
pub fn generate_targets<A: AsRef<Path>>(&self, amount: u32, range_to: RangeTo<u32>, path: A) -> Result<()> {
104+
pub fn generate_targets<A: AsRef<Path>>(&self, amount: u32, range_to: RangeTo<u32>, path: A) -> Result<(), GenerationError> {
83105
let start = Instant::now(); // start timer
84106

85107

@@ -89,45 +111,13 @@ impl TargetGenerator {
89111
Ok(())
90112
}
91113

92-
pub fn close(&self) {
93-
self.coco_generator.save();
114+
pub fn generate_new_location_no_collision(&self) -> (u32, u32) {
115+
todo!() // TODO: use collision detection to find a new location
94116
}
95-
}
96-
97-
const STANDARD_PPM: f32 = 35.0;
98-
99-
/// Use the real size of an object and the Pixels Per Meter value to calculate the size in
100-
/// pixels that it should be in order to be at scale
101-
fn resize_ratio(object_real_size: f32, pixels_per_meter: f32) -> f32 {
102-
debug!("Real size: {}, Pixels per meter: {}", object_real_size, pixels_per_meter);
103-
object_real_size * pixels_per_meter
104-
}
105-
106-
/// Calculate the new sizes of an object in pixels based on the requested Pixel Per Meter value
107-
/// 1. Calculate the aspect ratio
108-
/// 2. Calculate the width of the object in pixels that we expect based on the real width and the Pixels Per Meter value
109-
/// 3. Calculate the height from this new width using the previously calculated aspect ratio
110-
fn new_sizes(object_width: u32, object_height: u32, pixels_per_meter: f32, real_width: f32) -> Result<(u32, u32), GenerationError> {
111-
let (w, h) = (object_width as f32, object_height as f32);
112-
let aspect_ratio = w / h;
113-
let new_width = resize_ratio(real_width, pixels_per_meter) as u32;
114-
let new_height = (new_width as f32 / aspect_ratio) as u32;
115117

116-
if new_height == 0 || new_width == 0 {
117-
return Err(GenerationError::SizeError);
118+
pub fn close(&self) {
119+
self.coco_generator.save();
118120
}
119-
120-
Ok((new_width, new_height))
121-
}
122-
123-
#[derive(Debug, Error)]
124-
pub enum GenerationError {
125-
#[error("Serde decoding or encoding error")]
126-
SerdeError(#[from] serde_json::Error),
127-
#[error("IO error occurred while generating target")]
128-
IOError(#[from] std::io::Error),
129-
#[error("Calculated new sizes provided an invalid size")]
130-
SizeError
131121
}
132122

133123
#[test]
@@ -136,7 +126,7 @@ pub fn test_generate_target() {
136126
SimpleLogger::new().init().unwrap();
137127

138128
let mut tg = TargetGenerator::new("output", "backgrounds", "objects", "output/annotations.json").unwrap();
139-
let b = tg.generate_target(STANDARD_PPM).unwrap();
129+
let b = tg.generate_target(STANDARD_PPM, 1).unwrap();
140130

141131
b.save("output_1.png".to_string()).unwrap();
142132
debug!("Saved generated target to output_1.png");

src/generator/util.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use log::debug;
2+
use crate::generator::error::GenerationError;
3+
4+
pub const STANDARD_PPM: f32 = 35.0;
5+
6+
/// Use the real size of an object and the Pixels Per Meter value to calculate the size in
7+
/// pixels that it should be in order to be at scale
8+
fn resize_ratio(object_real_size: f32, pixels_per_meter: f32) -> f32 {
9+
debug!("Real size: {}, Pixels per meter: {}", object_real_size, pixels_per_meter);
10+
object_real_size * pixels_per_meter
11+
}
12+
13+
/// Calculate the new sizes of an object in pixels based on the requested Pixel Per Meter value
14+
/// 1. Calculate the aspect ratio
15+
/// 2. Calculate the width of the object in pixels that we expect based on the real width and the Pixels Per Meter value
16+
/// 3. Calculate the height from this new width using the previously calculated aspect ratio
17+
pub fn new_sizes(object_width: u32, object_height: u32, pixels_per_meter: f32, real_width: f32) -> anyhow::Result<(u32, u32), GenerationError> {
18+
let (w, h) = (object_width as f32, object_height as f32);
19+
let aspect_ratio = w / h;
20+
let new_width = resize_ratio(real_width, pixels_per_meter) as u32;
21+
let new_height = (new_width as f32 / aspect_ratio) as u32;
22+
23+
if new_height == 0 || new_width == 0 {
24+
return Err(GenerationError::SizeError);
25+
}
26+
27+
Ok((new_width, new_height))
28+
}

0 commit comments

Comments
 (0)