Skip to main content

Conversion

The conversion module provides structured reporting for format transformations, tracking what information is preserved, lost, or transformed during conversion.

Overview

When converting between formats, not all information can always be preserved. Panlabel analyzes your dataset and target format to report:
  • Warnings: Information that will be lost (e.g., metadata, attributes)
  • Info: Policy decisions (e.g., ID assignment, coordinate precision)
  • Counts: Input vs output statistics (e.g., dropped images)

Format Enum

The Format enum identifies annotation formats:
pub enum Format {
    IrJson,
    Coco,
    Cvat,
    LabelStudio,
    Tfod,
    Yolo,
    Voc,
}
Methods:
impl Format {
    pub fn name(&self) -> &'static str;
    pub fn lossiness_relative_to_ir(&self) -> IrLossiness;
}

Lossiness Classification

Formats are classified by how much IR information they can preserve:
pub enum IrLossiness {
    /// Format can represent everything in the IR (round-trip safe)
    Lossless,
    /// Format may lose some information depending on dataset content
    Conditional,
    /// Format always loses some IR information
    Lossy,
}
Format lossiness:
FormatLossinessNotes
IR JSONLosslessThe IR itself - preserves everything
COCOConditionalMay lose dataset name, some attributes
CVATLossyDrops metadata, licenses, confidence
Label StudioLossyDrops metadata, licenses, most attributes
TFODLossyDrops metadata, images without annotations
YOLOLossyDrops metadata, confidence, attributes
VOCLossyDrops metadata, confidence, some attributes

Building Conversion Reports

build_conversion_report

Analyze a conversion before executing it:
pub fn build_conversion_report(
    dataset: &Dataset,
    from: Format,
    to: Format,
) -> ConversionReport
Example:
use panlabel::conversion::{build_conversion_report, Format};
use panlabel::ir::io_coco_json;
use std::path::Path;

// Read dataset
let dataset = io_coco_json::read_coco_json(Path::new("data.json"))?;

// Analyze conversion
let report = build_conversion_report(
    &dataset,
    Format::Coco,
    Format::Yolo,
);

// Check if lossy
if report.is_lossy() {
    println!("Warning: This conversion will lose information:");
    println!("{}", report);
}

// Check counts
println!("Input: {} images", report.input.images);
println!("Output: {} images", report.output.images);

ConversionReport

The report structure contains detailed conversion information:
pub struct ConversionReport {
    pub from: String,
    pub to: String,
    pub input: ConversionCounts,
    pub output: ConversionCounts,
    pub issues: Vec<ConversionIssue>,
}
Methods:
impl ConversionReport {
    /// Returns true if any warnings were issued
    pub fn is_lossy(&self) -> bool;
    
    /// Count of warning issues
    pub fn warning_count(&self) -> usize;
    
    /// Count of info issues
    pub fn info_count(&self) -> usize;
}
Display: The report implements Display for human-readable output:
println!("{}", report);
Output:
Conversion: coco -> yolo
Input:  2 images, 2 categories, 3 annotations
Output: 2 images, 2 categories, 3 annotations

⚠ Warnings (5):
  - dataset info/metadata will be dropped
  - 1 license(s) will be dropped
  - 1 image(s) have license_id/date_captured that will be dropped
  - 1 category(s) have supercategory that will be dropped
  - 1 annotation(s) have confidence scores that will be dropped

ℹ Info (3):
  - YOLO writer assigns class indices by CategoryId order (sorted ascending)
  - YOLO writer creates empty .txt files for images without annotations
  - YOLO writer outputs normalized coordinates with 6 decimal places

ConversionCounts

Counts of dataset elements:
pub struct ConversionCounts {
    pub images: usize,
    pub categories: usize,
    pub annotations: usize,
}
Output counts may differ from input counts when:
  • Images without annotations are dropped (TFOD)
  • Categories not referenced by annotations are dropped (TFOD)

ConversionIssue

Individual conversion issues:
pub struct ConversionIssue {
    pub severity: ConversionSeverity,
    pub code: ConversionIssueCode,
    pub message: String,
}

pub enum ConversionSeverity {
    Warning,  // Information will be lost
    Info,     // Policy decision or transformation
}

Issue Codes

Stable, machine-readable codes for each type of issue:
pub enum ConversionIssueCode {
    // Lossiness warnings
    DropDatasetInfo,
    DropDatasetInfoName,
    DropLicenses,
    DropImageMetadata,
    DropCategorySupercategory,
    DropAnnotationConfidence,
    DropAnnotationAttributes,
    DropImagesWithoutAnnotations,
    
    // Format-specific
    CocoAttributesMayNotBePreserved,
    LabelStudioRotationDropped,
    
    // Reader policies
    TfodReaderIdAssignment,
    YoloReaderIdAssignment,
    YoloReaderClassMapSource,
    VocReaderIdAssignment,
    VocReaderAttributeMapping,
    VocReaderCoordinatePolicy,
    VocReaderDepthHandling,
    LabelStudioReaderIdAssignment,
    LabelStudioReaderImageRefPolicy,
    CvatReaderIdAssignment,
    CvatReaderAttributePolicy,
    
    // Writer policies
    TfodWriterRowOrder,
    YoloWriterClassOrder,
    YoloWriterEmptyLabelFiles,
    YoloWriterFloatPrecision,
    VocWriterFileLayout,
    VocWriterNoImageCopy,
    VocWriterBoolNormalization,
    LabelStudioWriterFromToDefaults,
    CvatWriterMetaDefaults,
}

Usage Patterns

Check Before Converting

use panlabel::conversion::{build_conversion_report, Format};

let report = build_conversion_report(&dataset, Format::Coco, Format::Tfod);

if report.is_lossy() {
    eprintln!("Warning: Conversion will lose information");
    eprintln!("{}", report);
    
    // Optionally block lossy conversions
    if !allow_lossy {
        return Err("Lossy conversion not allowed".into());
    }
}

// Proceed with conversion
io_tfod_csv::write_tfod_csv(path, &dataset)?;

Analyze Multiple Targets

let targets = vec![Format::Yolo, Format::Voc, Format::Tfod];

for target in targets {
    let report = build_conversion_report(&dataset, Format::Coco, target);
    
    println!("\n{} -> {}:", Format::Coco.name(), target.name());
    
    if report.is_lossy() {
        println!("  ⚠ {} warnings", report.warning_count());
    } else {
        println!("  ✓ Lossless");
    }
    
    if report.output.images < report.input.images {
        println!(
            "  ℹ {} images will be dropped",
            report.input.images - report.output.images
        );
    }
}

JSON Export

The report is serializable for programmatic use:
use serde_json;

let report = build_conversion_report(&dataset, Format::Coco, Format::Yolo);
let json = serde_json::to_string_pretty(&report)?;
println!("{}", json);
Output:
{
  "from": "coco",
  "to": "yolo",
  "input": {
    "images": 2,
    "categories": 2,
    "annotations": 3
  },
  "output": {
    "images": 2,
    "categories": 2,
    "annotations": 3
  },
  "issues": [
    {
      "severity": "Warning",
      "code": "DropDatasetInfo",
      "message": "dataset info/metadata will be dropped"
    }
  ]
}

Complete Example

use panlabel::conversion::{build_conversion_report, Format};
use panlabel::ir::{io_coco_json, io_yolo};
use panlabel::PanlabelError;
use std::path::Path;

fn safe_convert(
    input: &Path,
    output: &Path,
    allow_lossy: bool,
) -> Result<(), PanlabelError> {
    // Read source dataset
    let dataset = io_coco_json::read_coco_json(input)?;
    
    // Analyze conversion
    let report = build_conversion_report(
        &dataset,
        Format::Coco,
        Format::Yolo,
    );
    
    // Display report
    println!("{}", report);
    
    // Check if conversion is lossy
    if report.is_lossy() && !allow_lossy {
        return Err(PanlabelError::LossyConversionBlocked {
            from: "coco".to_string(),
            to: "yolo".to_string(),
            report: Box::new(report),
        });
    }
    
    // Perform conversion
    io_yolo::write_yolo_dir(output, &dataset)?;
    
    println!("\nConversion complete!");
    println!("  Input: {} images", report.input.images);
    println!("  Output: {} images", report.output.images);
    
    Ok(())
}

Next Steps

Build docs developers (and LLMs) love