panlabel 0.6.0

The universal annotation converter
Documentation
use std::path::PathBuf;
use thiserror::Error;

use crate::conversion::ConversionReport;
use crate::validation::ValidationReport;

/// The main error type for panlabel operations.
#[derive(Debug, Error)]
pub enum PanlabelError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Failed to parse IR JSON from {path}: {source}")]
    IrJsonParse {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Failed to write IR JSON to {path}: {source}")]
    IrJsonWrite {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Failed to parse COCO JSON from {path}: {source}")]
    CocoJsonParse {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Failed to write COCO JSON to {path}: {source}")]
    CocoJsonWrite {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Failed to parse Label Studio JSON from {path}: {source}")]
    LabelStudioJsonParse {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Failed to write Label Studio JSON to {path}: {source}")]
    LabelStudioJsonWrite {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Invalid Label Studio JSON: {path}: {message}")]
    LabelStudioJsonInvalid { path: PathBuf, message: String },

    #[error("Failed to parse TFOD CSV from {path}: {source}")]
    TfodCsvParse {
        path: PathBuf,
        #[source]
        source: csv::Error,
    },

    #[error("Failed to write TFOD CSV to {path}: {source}")]
    TfodCsvWrite {
        path: PathBuf,
        #[source]
        source: csv::Error,
    },

    #[error("Invalid TFOD CSV: {path}: {message}")]
    TfodCsvInvalid { path: PathBuf, message: String },

    #[error("Invalid YOLO dataset layout at {path}: {message}")]
    YoloLayoutInvalid { path: PathBuf, message: String },

    #[error("Failed to parse YOLO data.yaml at {path}: {source}")]
    YoloDataYamlParse {
        path: PathBuf,
        #[source]
        source: serde_yaml::Error,
    },

    #[error("Invalid YOLO classes.txt at {path}: {message}")]
    YoloClassesTxtInvalid { path: PathBuf, message: String },

    #[error("Failed to parse YOLO label row in {path}:{line}: {message}")]
    YoloLabelParse {
        path: PathBuf,
        line: usize,
        message: String,
    },

    #[error(
        "No matching image found for label file {label_path} (expected stem: {expected_stem})"
    )]
    YoloImageNotFound {
        label_path: PathBuf,
        expected_stem: String,
    },

    #[error("Failed to read YOLO image dimensions from {path}: {source}")]
    YoloImageDimensionRead {
        path: PathBuf,
        #[source]
        source: imagesize::ImageError,
    },

    #[error("Failed to write YOLO dataset at {path}: {message}")]
    YoloWriteError { path: PathBuf, message: String },

    #[error("Invalid VOC dataset layout at {path}: {message}")]
    VocLayoutInvalid { path: PathBuf, message: String },

    #[error("Failed to parse VOC XML from {path}: {message}")]
    VocXmlParse { path: PathBuf, message: String },

    #[error("Failed to write VOC dataset at {path}: {message}")]
    VocWriteError { path: PathBuf, message: String },

    #[error("Invalid KITTI dataset layout at {path}: {message}")]
    KittiLayoutInvalid { path: PathBuf, message: String },

    #[error("Failed to parse KITTI label in {path}:{line}: {message}")]
    KittiLabelParse {
        path: PathBuf,
        line: usize,
        message: String,
    },

    #[error("Failed to read KITTI image dimensions from {path}: {source}")]
    KittiImageDimensionRead {
        path: PathBuf,
        #[source]
        source: imagesize::ImageError,
    },

    #[error("Failed to write KITTI dataset at {path}: {message}")]
    KittiWriteError { path: PathBuf, message: String },

    #[error("Failed to parse VIA JSON from {path}: {source}")]
    ViaJsonParse {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Failed to write VIA JSON to {path}: {source}")]
    ViaJsonWrite {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Invalid VIA JSON at {path}: {message}")]
    ViaJsonInvalid { path: PathBuf, message: String },

    #[error("VIA image not found: {image_ref} (searched from {path})")]
    ViaImageNotFound { path: PathBuf, image_ref: String },

    #[error("Failed to parse RetinaNet CSV from {path}: {source}")]
    RetinanetCsvParse {
        path: PathBuf,
        #[source]
        source: csv::Error,
    },

    #[error("Failed to write RetinaNet CSV to {path}: {source}")]
    RetinanetCsvWrite {
        path: PathBuf,
        #[source]
        source: csv::Error,
    },

    #[error("Invalid RetinaNet CSV: {path}: {message}")]
    RetinanetCsvInvalid { path: PathBuf, message: String },

    #[error("RetinaNet image not found: {image_ref} (searched from {path})")]
    RetinanetImageNotFound { path: PathBuf, image_ref: String },

    #[error("Failed to read RetinaNet image dimensions from {path}: {source}")]
    RetinanetImageDimensionRead {
        path: PathBuf,
        #[source]
        source: imagesize::ImageError,
    },

    #[error("Invalid CVAT XML layout at {path}: {message}")]
    CvatLayoutInvalid { path: PathBuf, message: String },

    #[error("Failed to parse CVAT XML from {path}: {message}")]
    CvatXmlParse { path: PathBuf, message: String },

    #[error("Failed to write CVAT XML at {path}: {message}")]
    CvatWriteError { path: PathBuf, message: String },

    #[error("Failed to parse LabelMe JSON from {path}: {source}")]
    LabelMeJsonParse {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Failed to write LabelMe JSON to {path}: {source}")]
    LabelMeJsonWrite {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Invalid LabelMe dataset layout at {path}: {message}")]
    LabelMeLayoutInvalid { path: PathBuf, message: String },

    #[error("Failed to parse CreateML JSON from {path}: {source}")]
    CreateMlJsonParse {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Failed to write CreateML JSON to {path}: {source}")]
    CreateMlJsonWrite {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Invalid CreateML JSON at {path}: {message}")]
    CreateMlJsonInvalid { path: PathBuf, message: String },

    #[error("CreateML image not found: {image_ref} (searched from {path})")]
    CreateMlImageNotFound { path: PathBuf, image_ref: String },

    #[error("Failed to read CreateML image dimensions from {path}: {source}")]
    CreateMlImageDimensionRead {
        path: PathBuf,
        #[source]
        source: imagesize::ImageError,
    },

    #[error("Invalid HF ImageFolder layout at {path}: {message}")]
    HfLayoutInvalid { path: PathBuf, message: String },

    #[error("Failed to parse HF metadata.jsonl at {path}, line {line}: {message}")]
    HfJsonlParse {
        path: PathBuf,
        line: usize,
        message: String,
    },

    #[error("Failed to write HF metadata.jsonl at {path}: {message}")]
    HfWriteError { path: PathBuf, message: String },

    #[cfg(feature = "hf-parquet")]
    #[error("Failed to parse HF metadata.parquet at {path}: {message}")]
    HfParquetParse { path: PathBuf, message: String },

    #[cfg(feature = "hf-remote")]
    #[error("Invalid HF repo reference '{input}': {message}")]
    HfResolveError { input: String, message: String },

    #[cfg(feature = "hf-remote")]
    #[error("HF Hub API error for {repo_id}: {message}")]
    HfApiError { repo_id: String, message: String },

    #[cfg(feature = "hf-remote")]
    #[error("Failed to download from HF Hub ({repo_id}): {message}")]
    HfAcquireError { repo_id: String, message: String },

    #[cfg(feature = "hf-remote")]
    #[error("Unsupported HF zip payload for {repo_id}: {message}")]
    HfZipLayoutInvalid { repo_id: String, message: String },

    #[error("Validation failed with {error_count} error(s) and {warning_count} warning(s)")]
    ValidationFailed {
        error_count: usize,
        warning_count: usize,
        report: ValidationReport,
    },

    #[error("Unsupported format: {0}")]
    UnsupportedFormat(String),

    #[error("Failed to detect format for {path}: {reason}")]
    FormatDetectionFailed { path: PathBuf, reason: String },

    #[error("Failed to parse JSON while detecting format for {path}: {source}")]
    FormatDetectionJsonParse {
        path: PathBuf,
        #[source]
        source: serde_json::Error,
    },

    #[error("Lossy conversion from {from} to {to} is blocked — {warning_count} warning(s) found (use --allow-lossy to proceed; see report above)", warning_count = report.warning_count())]
    LossyConversionBlocked {
        from: String,
        to: String,
        report: Box<ConversionReport>,
    },

    #[error("Diff failed: {message}")]
    DiffFailed { message: String },

    #[error("Sample failed: {message}")]
    SampleFailed { message: String },

    #[error("Invalid sample parameters: {message}")]
    InvalidSampleParams { message: String },

    #[error("Failed to write report as JSON: {source}")]
    ReportJsonWrite {
        #[source]
        source: serde_json::Error,
    },
}