CoolFace
Apppublic

Blablablab/audio-classification

sourceHugging Faceapache-2.0updated 1mo agoView on Hugging Face
0likes
codebook_exporter.py158 linesDownload Raw Back to export
1"""2Codebook Exporter3 4Exports the project codebook (label/code taxonomy) to a CSV file with one row per5code. Designed for qualitative-research workflows where the codebook is a6deliverable in its own right.7 8Output columns:9    schema           annotation_scheme name10    annotation_type  schema type (radio, multiselect, span, hierarchical_multiselect)11    code             label name12    parent           parent code (for hierarchical schemas)13    description      description / tooltip from the schema config14    color            color hex if defined15    n_uses           number of times this code was applied across all annotators16"""17 18import csv19import logging20import os21from typing import Optional, Tuple22 23from .base import BaseExporter, ExportContext, ExportResult24 25logger = logging.getLogger(__name__)26 27 28# Schemas that contribute codes to a codebook export.29CODEBOOK_SCHEMA_TYPES = {30    "radio", "multiselect", "select", "likert",31    "span", "hierarchical_multiselect", "tree_annotation",32}33 34 35class CodebookExporter(BaseExporter):36    format_name = "codebook"37    description = "Project codebook (CSV) with code names, hierarchy, and use counts"38    file_extensions = [".csv"]39 40    def can_export(self, context: ExportContext) -> Tuple[bool, str]:41        has_codeable_schema = any(42            s.get("annotation_type") in CODEBOOK_SCHEMA_TYPES43            for s in context.schemas44        )45        if not has_codeable_schema:46            return False, "No codeable schema (radio/multiselect/span/etc.) in config"47        return True, ""48 49    def export(self, context: ExportContext, output_path: str,50               options: Optional[dict] = None) -> ExportResult:51        options = options or {}52        os.makedirs(output_path, exist_ok=True)53        out_file = os.path.join(output_path, "codebook.csv")54 55        use_counts = self._count_label_uses(context)56 57        rows = []58        for scheme in context.schemas:59            atype = scheme.get("annotation_type")60            if atype not in CODEBOOK_SCHEMA_TYPES:61                continue62            schema_name = scheme.get("name", "")63            for code_row in self._iter_codes(scheme):64                code_row["schema"] = schema_name65                code_row["annotation_type"] = atype66                code_row["n_uses"] = use_counts.get(67                    (schema_name, code_row["code"]), 068                )69                rows.append(code_row)70 71        fieldnames = [72            "schema", "annotation_type", "code", "parent",73            "description", "color", "n_uses",74        ]75        with open(out_file, "w", newline="", encoding="utf-8") as f:76            writer = csv.DictWriter(f, fieldnames=fieldnames)77            writer.writeheader()78            for r in rows:79                writer.writerow({k: r.get(k, "") for k in fieldnames})80 81        logger.info(f"Codebook exported to {out_file}: {len(rows)} codes")82        return ExportResult(83            success=True,84            format_name=self.format_name,85            files_written=[out_file],86            stats={"codes_exported": len(rows)},87        )88 89    @staticmethod90    def _iter_codes(scheme):91        """Yield {code, parent, description, color} dicts for a schema."""92        atype = scheme.get("annotation_type")93 94        if atype == "hierarchical_multiselect":95            yield from CodebookExporter._iter_hierarchical(scheme.get("labels", []), parent="")96            return97        if atype == "tree_annotation":98            yield from CodebookExporter._iter_hierarchical(scheme.get("labels", []), parent="")99            return100 101        labels = scheme.get("labels", [])102        for label in labels:103            if isinstance(label, dict):104                name = label.get("name", "")105                yield {106                    "code": name,107                    "parent": "",108                    "description": label.get("description") or label.get("tooltip", ""),109                    "color": label.get("color", ""),110                }111            else:112                yield {"code": str(label), "parent": "", "description": "", "color": ""}113 114    @staticmethod115    def _iter_hierarchical(nodes, parent):116        if not isinstance(nodes, list):117            return118        for node in nodes:119            if isinstance(node, dict):120                name = node.get("name", "")121                yield {122                    "code": name,123                    "parent": parent,124                    "description": node.get("description") or node.get("tooltip", ""),125                    "color": node.get("color", ""),126                }127                children = node.get("children") or node.get("labels") or []128                yield from CodebookExporter._iter_hierarchical(children, parent=name)129            else:130                yield {"code": str(node), "parent": parent, "description": "", "color": ""}131 132    @staticmethod133    def _count_label_uses(context):134        counts = {}135        for ann in context.annotations:136            labels = ann.get("labels", {}) or {}137            for schema_name, schema_payload in labels.items():138                names = []139                if isinstance(schema_payload, dict):140                    names = [k for k, v in schema_payload.items() if v]141                elif isinstance(schema_payload, list):142                    names = [str(x) for x in schema_payload]143                elif schema_payload not in (None, ""):144                    names = [str(schema_payload)]145                for n in names:146                    key = (schema_name, n)147                    counts[key] = counts.get(key, 0) + 1148 149            spans = ann.get("spans", {}) or {}150            for schema_name, span_list in spans.items():151                for span in span_list or []:152                    label = span.get("label") or span.get("annotation")153                    if label:154                        key = (schema_name, label)155                        counts[key] = counts.get(key, 0) + 1156 157        return counts158