Skip to content

Commit

Permalink
feat: add export structs (#13)
Browse files Browse the repository at this point in the history
* feat: add structs for parsing V7 dataset exports

* fix: add default for `AnnotationClassMetadata`
  • Loading branch information
TanyaSrinidhi authored May 15, 2023
1 parent 29cd605 commit 9c74449
Show file tree
Hide file tree
Showing 6 changed files with 406 additions and 204 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ serde_yaml = "0.9"
rand = "0.8.5"
uuid = { version = "1.3", features = ["v4", "serde"] }
async-trait = "0.1.66"
strum = { version = "0.24", features = ["derive"] }
strum_macros = "0.24"

[dev-dependencies]
tempfile = "3.3"
Expand Down
216 changes: 216 additions & 0 deletions src/annotation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
#[allow(unused_imports)]
use fake::{Dummy, Fake};

use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use strum_macros::{Display, EnumString};

use crate::client::V7Methods;
use crate::expect_http_ok;

#[derive(Debug, Clone, Serialize, Deserialize, Dummy, PartialEq, Eq, Default)]
pub struct AnnotationClassMetadata {
#[serde(rename = "_color")]
pub color: String,
pub polygon: Option<HashMap<String, String>>, // TODO find out what this type actually is
pub auto_annotate: Option<HashMap<String, String>>, // TODO find out what this type actually is
pub inference: Option<HashMap<String, String>>, // TODO find out what this type actually is
pub measures: Option<HashMap<String, String>>, // TODO find out what this type actually is
}

#[derive(Debug, Clone, Serialize, Deserialize, Dummy, Default)]
pub struct BoundingBox {
// Height of the bounding box
pub h: f32,
// Width of the bounding box
pub w: f32,
// Left-most coordinate of the bounding box
pub x: f32,
// Top-most coordinate of the bounding box
pub y: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize, Dummy, Default)]
pub struct Polygon {
pub path: Vec<Keypoint>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Dummy, Default)]
pub struct Keypoint {
// The horizontal coordinate of the keypoint
pub x: f32,
// The vertical coordinate of the key point
pub y: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize, Dummy, Default)]
pub struct Tag {}

#[derive(Debug, Clone, Serialize, Deserialize, Dummy, Default)]
pub struct Text {
pub text: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Dummy, Default)]
pub struct Line {
pub path: Vec<Keypoint>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Dummy, EnumString, Display)]
#[serde(rename_all = "lowercase")]
#[serde(untagged)]
pub enum AnnotationType {
Attributes,
#[serde(rename = "auto_annotate")]
AutoAnnotate,
#[serde(rename = "bounding_box")]
#[strum(serialize = "bounding_box")]
BoundingBox(BoundingBox),
Cuboid,
#[serde(rename = "directional_vector")]
DirectionalVector,
#[strum(serialize = "ellipse")]
Ellipse,
Inference,
#[serde(rename = "instance_id")]
InstanceId,
#[strum(serialize = "keypoint")]
Keypoint(Keypoint),
#[strum(serialize = "line")]
Line(Line),
Measures,
#[strum(serialize = "polygon")]
Polygon(Polygon),
Skeleton,
#[strum(serialize = "tag")]
Tag(Tag),
Text(Text),
}

// Various ids for annotation types and sub types
// Tag: 1
// Tag-Attributes: [5 1]
// Tag-Text: [6, 1]
// Tag-Attributes-Text: [5, 6, 1]
// Polygon: 3
// Polygon-Attributes: [5, 3]
// Polygon-Text: [6, 3]
// Polygon-DirectionalVector: [6 4 3]
// Polygon-InstanceId: [9 3]
// bbox: 2
// bbox-attributes: [2 5]
// bbox-tag: [2, 6]
// skeleton: 12
// skeleton-text: [12 6]
// line: 11
// line-text-instanceid: [6 9 11]
// keypoint: 7
// ellipse: 60
// cuboid: 8

impl From<AnnotationType> for u32 {
fn from(value: AnnotationType) -> u32 {
match value {
AnnotationType::Attributes => 5,
AnnotationType::AutoAnnotate => todo!(),
AnnotationType::BoundingBox(_) => 2,
AnnotationType::Cuboid => todo!(),
AnnotationType::DirectionalVector => todo!(),
AnnotationType::Ellipse => todo!(),
AnnotationType::Inference => todo!(),
AnnotationType::InstanceId => todo!(),
AnnotationType::Keypoint(_) => todo!(),
AnnotationType::Line(_) => 11,
AnnotationType::Measures => todo!(),
AnnotationType::Polygon(_) => 3,
AnnotationType::Skeleton => 12,
AnnotationType::Tag(_) => 1,
AnnotationType::Text(_) => 6,
}
}
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, Dummy, PartialEq, Eq)]
pub struct AnnotationDataset {
pub id: u32,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, Dummy)]
pub struct AnnotationClass {
#[serde(skip_serializing_if = "Option::is_none")]
pub annotation_class_image_url: Option<String>,

#[serde(skip_serializing_if = "Vec::is_empty")]
pub annotation_types: Vec<AnnotationType>,

#[serde(skip_serializing_if = "Option::is_none")]
pub annotation_type_ids: Option<Vec<u32>>,

#[serde(skip_serializing_if = "Option::is_none")]
pub dataset_id: Option<u32>,

// #[serde(skip_serializing_if = "Vec::is_empty")]
pub datasets: Vec<AnnotationDataset>,

#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<u32>,

#[serde(skip_serializing_if = "Option::is_none")]
pub team_id: Option<u32>,

// #[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,

// #[serde(skip_serializing_if = "Option::is_none")]
pub images: Vec<String>, // TODO: find out what this type is

#[serde(skip_serializing_if = "Option::is_none")]
pub inserted_at: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<AnnotationClassMetadata>,

#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<String>,
}

impl AnnotationClass {
pub async fn update<C>(&self, client: &C) -> Result<AnnotationClass>
where
C: V7Methods,
{
let endpoint = format!(
"annotation_classes/{}",
self.id.context("Annotation class is missing an id")?
);
let response = client.put(&endpoint, Some(&self)).await?;

expect_http_ok!(response, AnnotationClass)
}

pub async fn delete<C>(&self, client: &C) -> Result<()>
where
C: V7Methods,
{
let endpoint = format!(
"annotation_classes/{}",
self.id.context("Annotation class is missing an id")?
);

let response = client.delete::<AnnotationClass>(&endpoint, None).await?;

if response.status() != 204 {
bail!(format!(
"Invalid status code {} {}",
response.status(),
response.text().await?
));
}

Ok(())
}
}
7 changes: 4 additions & 3 deletions src/datasets.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::annotation::AnnotationClass;
use crate::client::V7Methods;
use crate::expect_http_ok;
use crate::filter::Filter;
use crate::item::{AddDataPayload, DatasetItem};
use crate::team::{AnnotationClass, TypeCount};
use crate::team::TypeCount;
use crate::workflow::WorkflowTemplate;
use anyhow::{bail, Context, Result};
use async_trait::async_trait;
Expand Down Expand Up @@ -67,12 +68,12 @@ pub struct Dataset {
pub work_prioritization: Option<String>,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, Dummy, PartialEq, Eq)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, Dummy)]
pub struct ExportMetadata {
pub annotation_classes: Vec<AnnotationClass>,
pub annotation_types: Vec<TypeCount>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, Dummy, PartialEq, Eq)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, Dummy)]
pub struct Export {
pub download_url: String,
pub format: String,
Expand Down
143 changes: 143 additions & 0 deletions src/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//! This file contains structures and methods that define the Darwin Export Format
//! https://docs.v7labs.com/v1.0/reference/darwin-json
use crate::annotation::AnnotationType;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Annotator {
// Email of the Annotator or reviewer on Darwin
pub email: String,
// Full name (first name + last name) of the annotator
// or reviewer
pub full_name: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]

pub struct ImageExport {
// Internal filename on Darwin
pub filename: String,
// Height of the image
pub height: u32,
// Original filename
pub original_filename: String,
// Path of file within Darwin
pub path: String,
// Sequence number is a monotonic increasing number
// for each file uploaded int a Darwin Dataset.
//seq: u64,
// The URL of the image thumbnail
pub thumbnail_url: String,
//THe URL within V7 of the image
pub url: String,
// Width of the image
pub width: u32,
// The URL of the image on Darwin
pub workview_url: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ImageAnnotation {
// ID of the annotation
pub id: Option<String>,
// The annotation class name
pub name: String,
// An optional list of annotators of the image
pub annotators: Option<Vec<Annotator>>,
// An optional list of reviewers of the image
pub reviewers: Option<Vec<Annotator>>,

//// The actual data of the annotation
#[serde(alias = "bounding_box", alias = "cuboid")]
#[serde(alias = "skeleton", alias = "tag")]
pub annotation_type_1: Option<AnnotationType>,

#[serde(alias = "ellipse", alias = "line")]
#[serde(alias = "keypoint", alias = "polygon")]
pub annotation_type_2: Option<AnnotationType>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JsonExport {
pub annotations: Vec<ImageAnnotation>,
pub dataset: String,
pub image: ImageExport,
}

#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;

#[test]
fn test_v7_annotation_export_lines() -> Result<()> {
let raw_json = r#"
{
"annotators": [
{
"email": "[email protected]",
"full_name": "ABC XYZ"
}
],
"id": "fb81c35c-716a-413a-81e8-16ae9c054490",
"line": {
"path": [
{
"x": 103.92,
"y": 196.48
},
{
"x": 192.83,
"y": 123.58
}
]
},
"name": "something"
}
"#;
let _: ImageAnnotation = serde_json::from_str(raw_json)?;
Ok(())
}

#[test]
fn test_full_v7_export_file() -> Result<()> {
let contents = r#"
{
"dataset": "Test Dataset",
"image": {
"filename": "xxxx-xxxx-xxxx-xxxx-xxxx.xxxx",
"height": 88149,
"original_filename": "xxxxx-xxxx-xxxx-xxxx-xxxx.xxxx",
"path": "/",
"thumbnail_url": "https://darwin.v7labs.com/api/images/999/thumbnail",
"url": "https://darwin.v7labs.com/api/images/999/original",
"width": 188688,
"workview_url": "https://darwin.v7labs.com/workview?dataset=999&image=54"
},
"annotations": [
{
"bounding_box": {
"h": 588.75,
"w": 630.9500000000116,
"x": 88527.01,
"y": 11805.9
},
"id": "770e4a19-a350-4d5e-964e-783512a508f9",
"name": "Cheese",
"polygon": {
"path": [
{
"x": 89094.67,
"y": 11924.8
}]
}
}
]
}
"#;
let export: JsonExport = serde_json::from_str(contents).expect("Error parsing V7 Export");
assert!(export.annotations[0].annotation_type_2.is_some());
Ok(())
}
}
Loading

0 comments on commit 9c74449

Please sign in to comment.