1use std::fmt::Display;
5
6use crate::{
7 Client, Error,
8 api::{AnnotationSetID, DatasetID, ProjectID, SampleID},
9};
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12
13#[cfg(feature = "polars")]
14use polars::prelude::*;
15
16#[derive(Clone, Eq, PartialEq, Debug)]
43pub enum FileType {
44 Image,
46 LidarPcd,
48 LidarDepth,
50 LidarReflect,
52 RadarPcd,
54 RadarCube,
56}
57
58impl std::fmt::Display for FileType {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 let value = match self {
61 FileType::Image => "image",
62 FileType::LidarPcd => "lidar.pcd",
63 FileType::LidarDepth => "lidar.png",
64 FileType::LidarReflect => "lidar.jpg",
65 FileType::RadarPcd => "radar.pcd",
66 FileType::RadarCube => "radar.png",
67 };
68 write!(f, "{}", value)
69 }
70}
71
72impl From<&str> for FileType {
73 fn from(s: &str) -> Self {
74 match s {
75 "image" => FileType::Image,
76 "lidar.pcd" => FileType::LidarPcd,
77 "lidar.png" => FileType::LidarDepth,
78 "lidar.jpg" => FileType::LidarReflect,
79 "radar.pcd" => FileType::RadarPcd,
80 "radar.png" => FileType::RadarCube,
81 _ => panic!("Invalid file type"),
82 }
83 }
84}
85
86#[derive(Clone, Eq, PartialEq, Debug)]
113pub enum AnnotationType {
114 Box2d,
116 Box3d,
118 Mask,
120}
121
122impl From<&str> for AnnotationType {
123 fn from(s: &str) -> Self {
124 match s {
125 "box2d" => AnnotationType::Box2d,
126 "box3d" => AnnotationType::Box3d,
127 "mask" => AnnotationType::Mask,
128 _ => panic!("Invalid annotation type"),
129 }
130 }
131}
132
133impl From<String> for AnnotationType {
134 fn from(s: String) -> Self {
135 s.as_str().into()
136 }
137}
138
139impl From<&String> for AnnotationType {
140 fn from(s: &String) -> Self {
141 s.as_str().into()
142 }
143}
144
145impl std::fmt::Display for AnnotationType {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 let value = match self {
148 AnnotationType::Box2d => "box2d",
149 AnnotationType::Box3d => "box3d",
150 AnnotationType::Mask => "mask",
151 };
152 write!(f, "{}", value)
153 }
154}
155
156#[derive(Deserialize, Clone, Debug)]
195pub struct Dataset {
196 id: DatasetID,
197 project_id: ProjectID,
198 name: String,
199 description: String,
200 cloud_key: String,
201 #[serde(rename = "createdAt")]
202 created: DateTime<Utc>,
203}
204
205impl Display for Dataset {
206 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
207 write!(f, "{} {}", self.uid(), self.name)
208 }
209}
210
211impl Dataset {
212 pub fn id(&self) -> DatasetID {
213 self.id
214 }
215
216 pub fn uid(&self) -> String {
217 self.id.to_string()
218 }
219
220 pub fn project_id(&self) -> ProjectID {
221 self.project_id
222 }
223
224 pub fn name(&self) -> &str {
225 &self.name
226 }
227
228 pub fn description(&self) -> &str {
229 &self.description
230 }
231
232 pub fn cloud_key(&self) -> &str {
233 &self.cloud_key
234 }
235
236 pub fn created(&self) -> &DateTime<Utc> {
237 &self.created
238 }
239
240 pub async fn project(&self, client: &Client) -> Result<crate::api::Project, Error> {
241 client.project(self.project_id).await
242 }
243
244 pub async fn annotation_sets(&self, client: &Client) -> Result<Vec<AnnotationSet>, Error> {
245 client.annotation_sets(self.id).await
246 }
247
248 pub async fn labels(&self, client: &Client) -> Result<Vec<Label>, Error> {
249 client.labels(self.id).await
250 }
251
252 pub async fn add_label(&self, client: &Client, name: &str) -> Result<(), Error> {
253 client.add_label(self.id, name).await
254 }
255
256 pub async fn remove_label(&self, client: &Client, name: &str) -> Result<(), Error> {
257 let labels = self.labels(client).await?;
258 let label = labels
259 .iter()
260 .find(|l| l.name() == name)
261 .ok_or_else(|| Error::MissingLabel(name.to_string()))?;
262 client.remove_label(label.id()).await
263 }
264}
265
266#[derive(Deserialize)]
270pub struct AnnotationSet {
271 id: AnnotationSetID,
272 dataset_id: DatasetID,
273 name: String,
274 description: String,
275 #[serde(rename = "date")]
276 created: DateTime<Utc>,
277}
278
279impl Display for AnnotationSet {
280 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
281 write!(f, "{} {}", self.uid(), self.name)
282 }
283}
284
285impl AnnotationSet {
286 pub fn id(&self) -> AnnotationSetID {
287 self.id
288 }
289
290 pub fn uid(&self) -> String {
291 self.id.to_string()
292 }
293
294 pub fn dataset_id(&self) -> DatasetID {
295 self.dataset_id
296 }
297
298 pub fn name(&self) -> &str {
299 &self.name
300 }
301
302 pub fn description(&self) -> &str {
303 &self.description
304 }
305
306 pub fn created(&self) -> DateTime<Utc> {
307 self.created
308 }
309
310 pub async fn dataset(&self, client: &Client) -> Result<Dataset, Error> {
311 client.dataset(self.dataset_id).await
312 }
313}
314
315#[derive(Serialize, Deserialize, Clone, Debug)]
316pub struct Sample {
317 id: SampleID,
318 #[serde(alias = "group_name", skip_serializing_if = "Option::is_none")]
319 group: Option<String>,
320 sequence_name: Option<String>,
321 image_name: String,
322 image_url: String,
323 #[serde(rename = "sensors")]
324 files: Option<Vec<SampleFile>>,
325 annotations: Option<Vec<Annotation>>,
326}
327
328impl Display for Sample {
329 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
330 write!(f, "{} {}", self.uid(), self.image_name())
331 }
332}
333
334impl Sample {
335 pub fn id(&self) -> SampleID {
336 self.id
337 }
338
339 pub fn uid(&self) -> String {
340 self.id.to_string()
341 }
342
343 pub fn name(&self) -> String {
344 let name = self
345 .image_name
346 .rsplit_once('.')
347 .map_or_else(|| self.image_name.clone(), |(name, _)| name.to_string());
348 name.rsplit_once(".camera")
349 .map_or_else(|| name.clone(), |(name, _)| name.to_string())
350 }
351
352 pub fn group(&self) -> Option<&String> {
353 self.group.as_ref()
354 }
355
356 pub fn sequence_name(&self) -> Option<&String> {
357 self.sequence_name.as_ref()
358 }
359
360 pub fn image_name(&self) -> &str {
361 &self.image_name
362 }
363
364 pub fn image_url(&self) -> &str {
365 &self.image_url
366 }
367
368 pub fn files(&self) -> &[SampleFile] {
369 match &self.files {
370 Some(files) => files,
371 None => &[],
372 }
373 }
374
375 pub fn annotations(&self) -> &[Annotation] {
376 match &self.annotations {
377 Some(annotations) => annotations,
378 None => &[],
379 }
380 }
381
382 pub fn with_annotations(mut self, annotations: Vec<Annotation>) -> Self {
383 self.annotations = Some(annotations);
384 self
385 }
386
387 pub async fn download(
388 &self,
389 client: &Client,
390 file_type: FileType,
391 ) -> Result<Option<Vec<u8>>, Error> {
392 let url = match file_type {
393 FileType::Image => Some(&self.image_url),
394 file => self
395 .files
396 .as_ref()
397 .and_then(|files| files.iter().find(|f| f.r#type == file.to_string()))
398 .map(|f| &f.url),
399 };
400
401 Ok(match url {
402 Some(url) => Some(client.download(url).await?),
403 None => None,
404 })
405 }
406}
407
408#[derive(Serialize, Deserialize, Clone, Debug)]
409pub struct SampleFile {
410 r#type: String,
411 url: String,
412}
413
414pub trait TypeName {
415 fn type_name() -> String;
416}
417
418#[derive(Serialize, Deserialize, Clone, Debug)]
419pub struct Box3d {
420 x: f32,
421 y: f32,
422 z: f32,
423 w: f32,
424 h: f32,
425 l: f32,
426}
427
428impl TypeName for Box3d {
429 fn type_name() -> String {
430 "box3d".to_owned()
431 }
432}
433
434impl Box3d {
435 pub fn new(cx: f32, cy: f32, cz: f32, width: f32, height: f32, length: f32) -> Self {
436 Self {
437 x: cx,
438 y: cy,
439 z: cz,
440 w: width,
441 h: height,
442 l: length,
443 }
444 }
445
446 pub fn width(&self) -> f32 {
447 self.w
448 }
449
450 pub fn height(&self) -> f32 {
451 self.h
452 }
453
454 pub fn length(&self) -> f32 {
455 self.l
456 }
457
458 pub fn cx(&self) -> f32 {
459 self.x
460 }
461
462 pub fn cy(&self) -> f32 {
463 self.y
464 }
465
466 pub fn cz(&self) -> f32 {
467 self.z
468 }
469
470 pub fn left(&self) -> f32 {
471 self.x - self.w / 2.0
472 }
473
474 pub fn top(&self) -> f32 {
475 self.y - self.h / 2.0
476 }
477
478 pub fn front(&self) -> f32 {
479 self.z - self.l / 2.0
480 }
481}
482
483#[derive(Serialize, Deserialize, Clone, Debug)]
484pub struct Box2d {
485 h: f32,
486 w: f32,
487 x: f32,
488 y: f32,
489}
490
491impl TypeName for Box2d {
492 fn type_name() -> String {
493 "box2d".to_owned()
494 }
495}
496
497impl Box2d {
498 pub fn new(left: f32, top: f32, width: f32, height: f32) -> Self {
499 Self {
500 x: left,
501 y: top,
502 w: width,
503 h: height,
504 }
505 }
506
507 pub fn width(&self) -> f32 {
508 self.w
509 }
510
511 pub fn height(&self) -> f32 {
512 self.h
513 }
514
515 pub fn left(&self) -> f32 {
516 self.x
517 }
518
519 pub fn top(&self) -> f32 {
520 self.y
521 }
522
523 pub fn cx(&self) -> f32 {
524 self.x + self.w / 2.0
525 }
526
527 pub fn cy(&self) -> f32 {
528 self.y + self.h / 2.0
529 }
530}
531
532#[derive(Serialize, Deserialize, Clone, Debug)]
533pub struct Mask {
534 pub polygon: Vec<Vec<(f32, f32)>>,
535}
536
537impl TypeName for Mask {
538 fn type_name() -> String {
539 "mask".to_owned()
540 }
541}
542
543impl Mask {
544 pub fn new(polygon: Vec<Vec<(f32, f32)>>) -> Self {
545 Self { polygon }
546 }
547}
548
549#[derive(Serialize, Deserialize, Clone, Debug)]
550pub struct Annotation {
551 #[serde(skip_serializing_if = "Option::is_none")]
552 sample_id: Option<SampleID>,
553 #[serde(skip_serializing_if = "Option::is_none")]
554 name: Option<String>,
555 #[serde(skip_serializing_if = "Option::is_none")]
556 sequence_name: Option<String>,
557 #[serde(skip_serializing_if = "Option::is_none")]
558 group: Option<String>,
559 #[serde(skip_serializing_if = "Option::is_none")]
560 object_id: Option<String>,
561 #[serde(alias = "label_name", skip_serializing_if = "Option::is_none")]
562 label: Option<String>,
563 #[serde(skip_serializing_if = "Option::is_none")]
564 label_index: Option<u64>,
565 #[serde(skip_serializing_if = "Option::is_none")]
566 box2d: Option<Box2d>,
567 #[serde(skip_serializing_if = "Option::is_none")]
568 box3d: Option<Box3d>,
569 #[serde(skip_serializing_if = "Option::is_none")]
570 mask: Option<Mask>,
571}
572
573impl Default for Annotation {
574 fn default() -> Self {
575 Self::new()
576 }
577}
578
579impl Annotation {
580 pub fn new() -> Self {
581 Self {
582 sample_id: None,
583 name: None,
584 sequence_name: None,
585 group: None,
586 object_id: None,
587 label: None,
588 label_index: None,
589 box2d: None,
590 box3d: None,
591 mask: None,
592 }
593 }
594
595 pub fn set_sample_id(&mut self, sample_id: Option<SampleID>) {
596 self.sample_id = sample_id;
597 }
598
599 pub fn sample_id(&self) -> Option<SampleID> {
600 self.sample_id
601 }
602
603 pub fn set_name(&mut self, name: Option<String>) {
604 self.name = name;
605 }
606
607 pub fn name(&self) -> Option<&String> {
608 self.name.as_ref()
609 }
610
611 pub fn set_sequence_name(&mut self, sequence_name: Option<String>) {
612 self.sequence_name = sequence_name;
613 }
614
615 pub fn sequence_name(&self) -> Option<&String> {
616 self.sequence_name.as_ref()
617 }
618
619 pub fn set_group(&mut self, group: Option<String>) {
620 self.group = group;
621 }
622
623 pub fn group(&self) -> Option<&String> {
624 self.group.as_ref()
625 }
626
627 pub fn object_id(&self) -> Option<&String> {
628 self.object_id.as_ref()
629 }
630
631 pub fn label(&self) -> Option<&String> {
632 self.label.as_ref()
633 }
634
635 pub fn label_index(&self) -> Option<u64> {
636 self.label_index
637 }
638
639 pub fn set_label_index(&mut self, label_index: Option<u64>) {
640 self.label_index = label_index;
641 }
642
643 pub fn box2d(&self) -> Option<&Box2d> {
644 self.box2d.as_ref()
645 }
646
647 pub fn box3d(&self) -> Option<&Box3d> {
648 self.box3d.as_ref()
649 }
650
651 pub fn mask(&self) -> Option<&Mask> {
652 self.mask.as_ref()
653 }
654}
655
656#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
657pub struct Label {
658 id: u64,
659 dataset_id: DatasetID,
660 index: u64,
661 name: String,
662}
663
664impl Label {
665 pub fn id(&self) -> u64 {
666 self.id
667 }
668
669 pub fn dataset_id(&self) -> DatasetID {
670 self.dataset_id
671 }
672
673 pub fn index(&self) -> u64 {
674 self.index
675 }
676
677 pub fn name(&self) -> &str {
678 &self.name
679 }
680
681 pub async fn remove(&self, client: &Client) -> Result<(), Error> {
682 client.remove_label(self.id()).await
683 }
684
685 pub async fn set_name(&mut self, client: &Client, name: &str) -> Result<(), Error> {
686 self.name = name.to_string();
687 client.update_label(self).await
688 }
689
690 pub async fn set_index(&mut self, client: &Client, index: u64) -> Result<(), Error> {
691 self.index = index;
692 client.update_label(self).await
693 }
694}
695
696impl Display for Label {
697 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
698 write!(f, "{}", self.name())
699 }
700}
701
702#[derive(Serialize, Clone, Debug)]
703pub struct NewLabelObject {
704 pub name: String,
705}
706
707#[derive(Serialize, Clone, Debug)]
708pub struct NewLabel {
709 pub dataset_id: DatasetID,
710 pub labels: Vec<NewLabelObject>,
711}
712
713#[derive(Deserialize, Clone, Debug)]
714pub struct Group {
715 pub id: u64, pub name: String,
717}
718
719#[cfg(feature = "polars")]
720pub fn annotations_dataframe(annotations: &[Annotation]) -> DataFrame {
721 use itertools::Itertools;
722 use log::warn;
723 use std::path::Path;
724
725 let (names, frames, objects, labels, label_indices, groups, masks, boxes2d, boxes3d) =
726 annotations
727 .iter()
728 .map(|ann| {
729 let name = match &ann.name {
730 Some(name) => name,
731 None => {
732 warn!("annotation missing image name, skipping");
733 return (
734 String::new(),
735 None,
736 None,
737 None,
738 None,
739 None,
740 None,
741 None,
742 None,
743 );
744 }
745 };
746
747 let name = Path::new(name).file_stem().unwrap().to_str().unwrap();
748
749 let (name, frame) = match &ann.sequence_name {
750 Some(sequence) => match name.strip_prefix(sequence) {
751 Some(frame) => (
752 sequence.to_string(),
753 Some(frame.trim_start_matches('_').to_string()),
754 ),
755 None => {
756 warn!(
757 "image_name {} does not match sequence_name {}",
758 name, sequence
759 );
760 return (
761 String::new(),
762 None,
763 None,
764 None,
765 None,
766 None,
767 None,
768 None,
769 None,
770 );
771 }
772 },
773 None => (name.to_string(), None),
774 };
775
776 let masks = match &ann.mask {
777 Some(seg) => {
778 use polars::series::Series;
779
780 let mut list = Vec::new();
781 for polygon in &seg.polygon {
782 for &(x, y) in polygon {
783 list.push(x);
784 list.push(y);
785 }
786 list.push(f32::NAN);
788 }
789
790 let list = if !list.is_empty() {
792 list[..list.len() - 1].to_vec()
793 } else {
794 vec![]
795 };
796
797 Some(Series::new("mask".into(), list))
798 }
799 None => Option::<Series>::None,
800 };
801
802 let box2d = ann.box2d.as_ref().map(|box2d| {
803 Series::new(
804 "box2d".into(),
805 [box2d.cx(), box2d.cy(), box2d.width(), box2d.height()],
806 )
807 });
808
809 let box3d = ann.box3d.as_ref().map(|box3d| {
810 Series::new(
811 "box3d".into(),
812 [box3d.x, box3d.y, box3d.z, box3d.w, box3d.h, box3d.l],
813 )
814 });
815
816 (
817 name,
818 frame,
819 ann.object_id.clone(),
820 ann.label.clone(),
821 ann.label_index,
822 ann.group.clone(),
823 masks,
824 box2d,
825 box3d,
826 )
827 })
828 .multiunzip::<(
829 Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, )>();
839 let names = Series::new("name".into(), names).into();
840 let frames = Series::new("frame".into(), frames).into();
841 let objects = Series::new("object_id".into(), objects).into();
842 let labels = Series::new("label".into(), labels)
843 .cast(&DataType::Categorical(
844 Categories::new("labels".into(), "labels".into(), CategoricalPhysical::U8),
845 Arc::new(CategoricalMapping::new(u8::MAX as usize)),
846 ))
847 .unwrap()
848 .into();
849 let label_indices = Series::new("label_index".into(), label_indices).into();
850 let groups = Series::new("group".into(), groups)
851 .cast(&DataType::Categorical(
852 Categories::new("groups".into(), "groups".into(), CategoricalPhysical::U8),
853 Arc::new(CategoricalMapping::new(u8::MAX as usize)),
854 ))
855 .unwrap()
856 .into();
857 let masks = Series::new("mask".into(), masks)
858 .cast(&DataType::List(Box::new(DataType::Float32)))
859 .unwrap()
860 .into();
861 let boxes2d = Series::new("box2d".into(), boxes2d)
862 .cast(&DataType::Array(Box::new(DataType::Float32), 4))
863 .unwrap()
864 .into();
865 let boxes3d = Series::new("box3d".into(), boxes3d)
866 .cast(&DataType::Array(Box::new(DataType::Float32), 6))
867 .unwrap()
868 .into();
869
870 DataFrame::new(vec![
871 names,
872 frames,
873 objects,
874 labels,
875 label_indices,
876 groups,
877 masks,
878 boxes2d,
879 boxes3d,
880 ])
881 .unwrap()
882}