-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add structs for parsing V7 dataset exports * fix: add default for `AnnotationClassMetadata`
- Loading branch information
1 parent
29cd605
commit 9c74449
Showing
6 changed files
with
406 additions
and
204 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
Oops, something went wrong.