pineapple_core/im/
boxes.rs1use std::fs::File;
5use std::io::{BufWriter, Read};
6use std::path::Path;
7
8use serde::Serialize;
9use serde_json::Value;
10
11use crate::constant::BOUNDING_BOX_JSON_VALID_KEYS;
12use crate::error::PineappleError;
13
14#[derive(Debug, Clone)]
34pub struct BoundingBoxes {
35 data: Vec<[f32; 4]>,
36}
37
38impl BoundingBoxes {
39 pub fn new(data: Vec<[f32; 4]>) -> Result<Self, PineappleError> {
54 let n = data.len();
55
56 let data: Vec<[f32; 4]> = data
57 .into_iter()
58 .filter(|[min_x, min_y, max_x, max_y]| max_x >= min_x && max_y >= min_y)
59 .collect();
60
61 if data.len() != n {
62 return Err(PineappleError::BoxesSizeError);
63 }
64
65 Ok(Self { data })
66 }
67}
68
69impl BoundingBoxes {
72 pub fn open<P: AsRef<Path>>(path: P) -> Result<BoundingBoxes, PineappleError> {
85 let extension = path
86 .as_ref()
87 .extension()
88 .and_then(|s| s.to_str())
89 .map(|s| s.to_lowercase());
90
91 if let Some(ext) = extension && ext == "json" {
92 return read_boxes_json(path);
93 }
94
95 Err(PineappleError::BoxesReadError)
96 }
97
98 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), PineappleError> {
112 let extension = path
113 .as_ref()
114 .extension()
115 .and_then(|s| s.to_str())
116 .map(|s| s.to_lowercase());
117
118 if let Some(ext) = extension && ext == "json" {
119 return write_boxes_json(path, &self.data);
120 }
121
122 Err(PineappleError::BoxesWriteError)
123 }
124}
125
126impl BoundingBoxes {
131 pub fn len(&self) -> usize {
133 self.data.len()
134 }
135
136 pub fn is_empty(&self) -> bool {
138 self.data.len() == 0
139 }
140}
141
142impl BoundingBoxes {
147 pub fn as_xyxy(&self) -> &Vec<[f32; 4]> {
149 &self.data
150 }
151
152 pub fn to_xyxy(self) -> Vec<[f32; 4]> {
154 self.data
155 }
156
157 pub fn to_xywh(self) -> Vec<[f32; 4]> {
159 self.data
160 .into_iter()
161 .map(|[min_x, min_y, max_x, max_y]| [min_x, min_y, max_x - min_x, max_y - min_y])
162 .collect()
163 }
164}
165
166impl BoundingBoxes {
171 pub fn remove(&mut self, indices: &[usize]) {
173 if indices.is_empty() {
174 return;
175 }
176
177 let mut data: Vec<[f32; 4]> = Vec::with_capacity(self.len() - indices.len());
178 let mut indices_iter = indices.iter().peekable();
179 let mut next_remove = indices_iter.next().copied();
180
181 for (idx, bounding_box) in self.data.iter().enumerate() {
182 if Some(idx) == next_remove {
183 next_remove = indices_iter.next().copied();
184 } else {
185 data.push(*bounding_box)
186 }
187 }
188
189 self.data = data;
190 }
191}
192
193pub fn read_boxes_json<P: AsRef<Path>>(path: P) -> Result<BoundingBoxes, PineappleError> {
197 let mut contents = String::new();
198
199 File::open(path)
200 .map_err(|err| PineappleError::NoFileError(err.to_string()))?
201 .read_to_string(&mut contents)
202 .map_err(|err| PineappleError::NoFileError(err.to_string()))?;
203
204 let data: Value = serde_json::from_str(&contents).map_err(|_| PineappleError::BoxesReadError)?;
205
206 fn to_f32(value: &Value) -> Result<f32, PineappleError> {
207 if let Some(n) = value.as_f64() {
208 Ok(n as f32)
209 } else if let Some(n) = value.as_u64() {
210 Ok(n as f32)
211 } else if let Some(n) = value.as_i64() {
212 Ok(n as f32)
213 } else {
214 Err(PineappleError::BoxesReadError)
215 }
216 }
217
218 for key in &BOUNDING_BOX_JSON_VALID_KEYS {
219 if let Some(boxes) = data.get(key).and_then(|v| v.as_array()) {
220 let boxes: Result<Vec<[f32; 4]>, _> = boxes
221 .iter()
222 .map(|item| {
223 item.as_array()
224 .ok_or(PineappleError::BoxesReadError)
225 .and_then(|b| {
226 if b.len() == 4 {
227 let min_x = to_f32(&b[0])?;
228 let min_y = to_f32(&b[1])?;
229 let max_x = to_f32(&b[2])?;
230 let max_y = to_f32(&b[3])?;
231 Ok([min_x, min_y, max_x, max_y])
232 } else {
233 Err(PineappleError::BoxesReadError)
234 }
235 })
236 })
237 .collect();
238
239 if let Ok(boxes) = boxes {
240 return BoundingBoxes::new(boxes);
241 }
242 };
243 }
244
245 Err(PineappleError::BoxesReadError)
246}
247
248pub fn write_boxes_json<P, T>(path: P, boxes: &Vec<[T; 4]>) -> Result<(), PineappleError>
250where
251 P: AsRef<Path>,
252 T: Serialize,
253{
254 let file = File::create(path).map_err(|_| PineappleError::BoxesWriteError)?;
255 let writer = BufWriter::new(file);
256
257 serde_json::to_writer(writer, &serde_json::json!({ "bounding_boxes": boxes }))
258 .map_err(|_| PineappleError::BoxesWriteError)?;
259
260 Ok(())
261}
262
263#[cfg(test)]
264mod test {
265
266 use super::*;
267
268 const TEST_DATA_JSON: &str = "../data/tests/test_boxes.json";
269
270 #[test]
271 pub fn test_open_json_success() {
272 let bounding_boxes = BoundingBoxes::open(TEST_DATA_JSON);
273 assert!(bounding_boxes.is_ok());
274 }
275
276 #[test]
277 pub fn test_open_json_failure() {
278 let bounding_boxes = BoundingBoxes::open("does_not_exist/");
279 assert!(bounding_boxes.is_err())
280 }
281
282 #[test]
283 pub fn test_open_json_format() {
284 let bounding_boxes = BoundingBoxes::open(TEST_DATA_JSON).unwrap();
285
286 for (i, bounding_box) in bounding_boxes.as_xyxy().iter().enumerate() {
287 assert_eq!(*bounding_box, [0_f32, 0_f32, i as f32, i as f32]);
288 }
289 }
290
291 #[test]
292 pub fn test_write_json() {
293 const OUTPUT: &str = "TEST_BOX_WRITE.json";
294
295 let bounding_boxes = BoundingBoxes::open(TEST_DATA_JSON).unwrap();
296
297 bounding_boxes.save(OUTPUT).unwrap();
298
299 let reloaded_boxes = BoundingBoxes::open(OUTPUT).unwrap();
300
301 assert_eq!(bounding_boxes.as_xyxy(), reloaded_boxes.as_xyxy());
302
303 std::fs::remove_file(OUTPUT).unwrap();
304 }
305}