@@ -79,6 +79,7 @@ impl CocoConverter {
7979 data : & NDJSONData ,
8080 _split : & str ,
8181 num_kpts : usize ,
82+ kpt_dims : usize ,
8283 ) -> String {
8384 let class_names = get_class_list ( data) ;
8485 let now = Utc :: now ( ) ;
@@ -177,30 +178,29 @@ impl CocoConverter {
177178 }
178179 }
179180 "pose" => {
180- for pose in img. get_pose_annotations ( ) {
181+ for pose in img. get_pose_annotations ( kpt_dims ) {
181182 let x_min = ( pose. bbox_x - pose. bbox_w / 2.0 ) * img. width as f64 ;
182183 let y_min = ( pose. bbox_y - pose. bbox_h / 2.0 ) * img. height as f64 ;
183184 let w = pose. bbox_w * img. width as f64 ;
184185 let h = pose. bbox_h * img. height as f64 ;
185186
186- let mut kps: Vec < f64 > = Vec :: new ( ) ;
187+ let mut kps = Vec :: with_capacity ( pose . num_keypoints ( ) * 3 ) ;
187188 let mut visible_count = 0 ;
188- for ( kp_x, kp_y, kp_v) in & pose. keypoints {
189- let abs_x = kp_x * img. width as f64 ;
190- let abs_y = kp_y * img. height as f64 ;
191- if * kp_v > 0.0 {
189+ for keypoint in pose. keypoints . chunks_exact ( pose. dims ) {
190+ let abs_x = keypoint[ 0 ] * img. width as f64 ;
191+ let abs_y = keypoint[ 1 ] * img. height as f64 ;
192+ let visibility = if pose. dims == 3 {
193+ keypoint[ 2 ]
194+ } else {
195+ // COCO requires visibility; 2D NDJSON points are labeled and visible.
196+ 2.0
197+ } ;
198+ if visibility > 0.0 {
192199 visible_count += 1 ;
193200 }
194201 kps. push ( abs_x) ;
195202 kps. push ( abs_y) ;
196- kps. push ( * kp_v) ;
197- }
198-
199- // Pad missing keypoints with 0,0,0 (not labeled)
200- for _ in pose. keypoints . len ( ) ..num_kpts {
201- kps. push ( 0.0 ) ;
202- kps. push ( 0.0 ) ;
203- kps. push ( 0.0 ) ;
203+ kps. push ( visibility) ;
204204 }
205205
206206 coco. annotations . push ( CocoAnnotation {
@@ -291,24 +291,18 @@ impl Converter for CocoConverter {
291291 let mut files: HashMap < String , Vec < u8 > > = HashMap :: new ( ) ;
292292 let task = & data. metadata . task ;
293293
294- // For pose: compute max keypoint count globally (max of metadata and actual data)
295- let num_kpts = if task == "pose" {
296- let meta_kpts = data
297- . metadata
294+ // convert_ndjson validates pose rows and populates kpt_shape before conversion.
295+ let ( num_kpts, kpt_dims) = if task == "pose" {
296+ data. metadata
298297 . kpt_shape
299298 . as_ref ( )
300- . and_then ( |s| s. first ( ) . copied ( ) )
301- . unwrap_or ( 0 ) as usize ;
302- let data_kpts = data
303- . images
304- . iter ( )
305- . flat_map ( |img| img. get_pose_annotations ( ) )
306- . map ( |p| p. keypoints . len ( ) )
307- . max ( )
308- . unwrap_or ( 0 ) ;
309- meta_kpts. max ( data_kpts)
299+ . and_then ( |shape| match shape. as_slice ( ) {
300+ [ num_keypoints, dims] => Some ( ( * num_keypoints as usize , * dims as usize ) ) ,
301+ _ => None ,
302+ } )
303+ . unwrap_or ( ( 0 , 3 ) )
310304 } else {
311- 0
305+ ( 0 , 0 )
312306 } ;
313307
314308 let splits = [
@@ -333,7 +327,7 @@ impl Converter for CocoConverter {
333327 }
334328
335329 // Create JSON at {split}/_annotations.coco.json
336- let coco_json = self . create_coco_json ( images, data, split, num_kpts) ;
330+ let coco_json = self . create_coco_json ( images, data, split, num_kpts, kpt_dims ) ;
337331 files. insert (
338332 format ! ( "{}/_annotations.coco.json" , split) ,
339333 coco_json. into_bytes ( ) ,
@@ -449,4 +443,89 @@ mod tests {
449443 Some ( "img1__abcd1234.jpg" )
450444 ) ;
451445 }
446+
447+ #[ test]
448+ fn pose_conversion_synthesizes_coco_visibility_for_two_dimensional_keypoints ( ) {
449+ let data = NDJSONData {
450+ metadata : DatasetMetadata {
451+ r#type : "dataset" . to_string ( ) ,
452+ task : "pose" . to_string ( ) ,
453+ name : "pose" . to_string ( ) ,
454+ description : String :: new ( ) ,
455+ bytes : 0 ,
456+ url : String :: new ( ) ,
457+ class_names : HashMap :: from ( [ ( "0" . to_string ( ) , "object" . to_string ( ) ) ] ) ,
458+ kpt_shape : Some ( vec ! [ 2 , 2 ] ) ,
459+ version : "1" . to_string ( ) ,
460+ } ,
461+ images : vec ! [ ImageEntry {
462+ r#type: "image" . to_string( ) ,
463+ file: "pose.jpg" . to_string( ) ,
464+ output_file: None ,
465+ url: String :: new( ) ,
466+ width: 100 ,
467+ height: 200 ,
468+ split: "train" . to_string( ) ,
469+ annotations: Some ( json!( {
470+ "pose" : [ [ 0 , 0.5 , 0.5 , 0.4 , 0.6 , 0.1 , 0.2 , 0.3 , 0.4 ] ]
471+ } ) ) ,
472+ } ] ,
473+ } ;
474+
475+ let files = CocoConverter :: new ( ) . convert ( & data, & HashMap :: new ( ) ) ;
476+ let coco: serde_json:: Value =
477+ serde_json:: from_slice ( files. get ( "train/_annotations.coco.json" ) . unwrap ( ) ) . unwrap ( ) ;
478+ let annotation = & coco[ "annotations" ] [ 0 ] ;
479+
480+ assert_eq ! (
481+ annotation[ "keypoints" ] ,
482+ json!( [ 10.0 , 40.0 , 2.0 , 30.0 , 80.0 , 2.0 ] )
483+ ) ;
484+ assert_eq ! ( annotation[ "num_keypoints" ] , 2 ) ;
485+ assert_eq ! ( annotation[ "keypoints" ] . as_array( ) . unwrap( ) . len( ) , 6 ) ;
486+ }
487+
488+ #[ test]
489+ fn pose_conversion_preserves_three_dimensional_visibility ( ) {
490+ let data = NDJSONData {
491+ metadata : DatasetMetadata {
492+ r#type : "dataset" . to_string ( ) ,
493+ task : "pose" . to_string ( ) ,
494+ name : "pose" . to_string ( ) ,
495+ description : String :: new ( ) ,
496+ bytes : 0 ,
497+ url : String :: new ( ) ,
498+ class_names : HashMap :: from ( [ ( "0" . to_string ( ) , "object" . to_string ( ) ) ] ) ,
499+ kpt_shape : Some ( vec ! [ 2 , 3 ] ) ,
500+ version : "1" . to_string ( ) ,
501+ } ,
502+ images : vec ! [ ImageEntry {
503+ r#type: "image" . to_string( ) ,
504+ file: "pose.jpg" . to_string( ) ,
505+ output_file: None ,
506+ url: String :: new( ) ,
507+ width: 100 ,
508+ height: 200 ,
509+ split: "train" . to_string( ) ,
510+ annotations: Some ( json!( {
511+ "pose" : [ [ 0 , 0.5 , 0.5 , 0.4 , 0.6 , 0.1 , 0.2 , 1 , 0.3 , 0.4 , 2 ] ]
512+ } ) ) ,
513+ } ] ,
514+ } ;
515+
516+ let files = CocoConverter :: new ( ) . convert ( & data, & HashMap :: new ( ) ) ;
517+ let coco: serde_json:: Value =
518+ serde_json:: from_slice ( files. get ( "train/_annotations.coco.json" ) . unwrap ( ) ) . unwrap ( ) ;
519+ let annotation = & coco[ "annotations" ] [ 0 ] ;
520+
521+ assert_eq ! (
522+ annotation[ "keypoints" ] ,
523+ json!( [ 10.0 , 40.0 , 1.0 , 30.0 , 80.0 , 2.0 ] )
524+ ) ;
525+ assert_eq ! ( annotation[ "num_keypoints" ] , 2 ) ;
526+ assert_eq ! (
527+ coco[ "categories" ] [ 0 ] [ "keypoints" ] . as_array( ) . unwrap( ) . len( ) ,
528+ 2
529+ ) ;
530+ }
452531}
0 commit comments