tao-route-visual-changenet-samples

Par nvidia · skills

Achemine les échantillons VCN les plus faibles (sortie de `tao-analyze-gaps-visual-changenet`) vers chaque module d'augmentation

npx skills add https://github.com/nvidia/skills --skill tao-route-visual-changenet-samples

Compétence de routage d'exemple TAO VCN

Vous êtes le répartiteur entre l'analyse des écarts et les modules d'augmentation dans un pipeline VCN AOI SDA. Chaque module d'augmentation ne peut agir que sur les labels qu'il sait gérer :

  • k-NN Mining ne peut extraire les voisins d'images réelles que pour les labels qui existent déjà dans le CSV du pool source. Il n'y a pas d'intérêt à chercher des voisins SHIFT si le pool n'a pas de lignes SHIFT.
  • AnomalyGen (Cosmos SDG) ne peut générer d'anomalies synthétiques que pour les classes que son pipeline d'inférence supporte : PASS, EXCESS_SOLDER, MISSING, BRIDGE. Un échantillon faible avec un label en dehors de cet ensemble ne peut pas être routé vers AnomalyGen.

Cette compétence s'exécute une fois par itération SDA immédiatement après l'analyse des écarts. Elle divise le parquet d'analyse des écarts en un parquet filtré par module afin que chaque module opère sur son propre sous-ensemble éligible, et écrit un résumé lisible par l'homme des décisions de routage par label.

Le travail est volontairement trivial : lire un parquet, appliquer deux filtres .isin(...), écrire deux parquets, écrire un résumé. La compétence existe pour rendre ces décisions auditables — chaque label doit apparaître dans le résumé avec un verdict oui/non pour chaque module afin qu'un relecteur en aval puisse détecter quand un label est silencieusement supprimé parce qu'aucun module ne l'a accepté.


Entrées

  1. gaps_parquet — la sortie d'analyse des écarts (typiquement <exp_dir>/rca_results/<timestamp>/gaps.parquet de tao-analyze-gaps-visual-changenet). Colonnes requises : filepath, label. Les autres colonnes (siamese_score, weakness) sont préservées telles quelles.
  2. source_pool_csv — CSV du pool source au format VCN avec une colonne label. Une chaîne vide ou un chemin inexistant est autorisé ; le sous-ensemble d'extraction sera simplement vide dans ce cas.
  3. Répertoire de sortie — où les deux parquets routés, le résumé et le rapport sont écrits. Par défaut : un dossier horodaté sous le répertoire de résultats d'analyse des écarts : <rca_result_dir>/routing_results/<timestamp>/.
  4. anomalygen_supported_labels (optionnel) — remplace l'ensemble de labels éligibles d'AnomalyGen par défaut. Par défaut : {"PASS", "EXCESS_SOLDER", "MISSING", "BRIDGE"}. Avertissement : Ceci doit rester synchronisé avec ANOMALYGEN_SUPPORTED_LABELS dans mdo-kratos-workflows/pipelines/sda/routing.py et la couverture générateur réelle de l'intégration AnomalyGen. Ajouter une nouvelle classe de défaut à AnomalyGen signifie l'ajouter ici aussi.

Méthode

Toute la compétence est deux masques .isin(...) sur la colonne label en majuscules.

Étape 1 — Charger et mettre en majuscules

df = pd.read_parquet(gaps_parquet)
labels_upper = df["label"].astype(str).str.upper()

La correspondance est insensible à la casse pour les deux vérifications de module. La colonne label originale est préservée inchangée dans les parquets de sortie — seule la clé de comparaison est mise en majuscules.

Étape 2 — Sous-ensemble d'extraction

if source_pool_csv and os.path.isfile(source_pool_csv):
    pool_df = pd.read_csv(source_pool_csv)
    pool_labels = {str(l).upper() for l in pool_df["label"].unique()}
    mn_mask = labels_upper.isin(pool_labels)
    mn_df = df[mn_mask]
else:
    pool_missing = True
    pool_labels = set()
    mn_df = df.iloc[0:0]   # vide, mais avec le même schéma
mn_df.to_parquet(mining_gaps_parquet, index=False)

Si le CSV du pool manque ou est vide, le sous-ensemble d'extraction est un DataFrame vide avec les mêmes colonnes que l'entrée afin que les lecteurs en aval ne plantent pas sur une incompatibilité de schéma. Signalez ce cas dans le résumé.

Étape 3 — Sous-ensemble AnomalyGen

ANOMALYGEN_SUPPORTED = {"PASS", "EXCESS_SOLDER", "MISSING", "BRIDGE"}
ag_mask = labels_upper.isin(ANOMALYGEN_SUPPORTED)
ag_df = df[ag_mask]
ag_df.to_parquet(anomalygen_gaps_parquet, index=False)

Les lignes dont le label est dans l'ensemble supporté d'AnomalyGen sont écrites telles quelles dans anomalygen_gaps.parquet. Le schéma correspond exactement au parquet d'entrée — AnomalyGen en aval (Cosmos SDG) n'a besoin d'aucune autre modification.

Étape 4 — Ventilation du routage par label

Pour chaque label distinct dans le parquet des écarts d'entrée (en majuscules), enregistrez :

  • count — combien de lignes ont ce label
  • mining — oui si le label est dans pool_labels, sinon non
  • anomalygen — oui si le label est dans ANOMALYGEN_SUPPORTED, sinon non

Un label peut router vers les deux modules (par ex. les lignes PASS vont vers AnomalyGen, et si le pool source contient aussi des lignes PASS elles vont vers Mining aussi). Un label peut aussi router vers aucun — signalez-les, car ils sont silencieusement supprimés et peuvent indiquer une incompatibilité de configuration.

Écrivez la ventilation dans routing_summary.txt. Le format correspond exactement à la composante de référence :

Weak-sample routing summary
Total weak samples: <N>
Mining subset:      <N_mn> -> <mining_gaps_parquet>
AnomalyGen subset:  <N_ag> -> <anomalygen_gaps_parquet>

[Si pool manquant :]
No source pool CSV at '<path>'; mining subset is empty.

Per-label breakdown (count, mining, anomalygen):
  PASS: 50 (mining=yes, anomalygen=yes)
  MISSING: 32 (mining=no, anomalygen=yes)
  SHIFT: 14 (mining=yes, anomalygen=no)
  EXCESS_SOLDER: 9 (mining=yes, anomalygen=yes)
  ...

Étape 5 — Vérifications de cohérence

Après l'écriture des deux sous-ensembles, vérifiez :

  • La somme des tailles de sous-ensemble n'est pas requise d'être égale à len(df) — le chevauchement est autorisé (un label peut router vers les deux modules). Ce qui importe est que chaque ligne d'entrée apparaisse dans au moins un sous-ensemble, OU apparaisse dans la liste « aucun » avec une raison explicite.
  • Si len(mn_df) == 0 et len(ag_df) == 0, quelque chose ne va pas — signalez-le de manière visible dans le rapport.
  • Si un groupe de labels entier route vers aucun module, la section Recommended Actions doit l'appeler afin que l'utilisateur puisse soit amorcer le pool source avec ce label, soit étendre l'ensemble supporté d'AnomalyGen.

Recette Python de référence

C'est le calcul exact, tiré de mdo-kratos-workflows/pipelines/sda/routing.py. Exécutez en tant que script Python unique via Bash ; il produit tous les artéfacts sauf le rapport.

import os
import pandas as pd

ANOMALYGEN_SUPPORTED = {"PASS", "EXCESS_SOLDER", "MISSING", "BRIDGE"}

df = pd.read_parquet(gaps_parquet)
labels_upper = df["label"].astype(str).str.upper()

# Sous-ensemble d'extraction
pool_missing = False
if source_pool_csv and os.path.isfile(source_pool_csv):
    pool_df = pd.read_csv(source_pool_csv)
    pool_labels = {str(l).upper() for l in pool_df["label"].unique()}
    mn_mask = labels_upper.isin(pool_labels)
    mn_df = df[mn_mask]
else:
    pool_missing = True
    pool_labels = set()
    mn_df = df.iloc[0:0]
os.makedirs(os.path.dirname(mining_gaps_parquet) or ".", exist_ok=True)
mn_df.to_parquet(mining_gaps_parquet, index=False)

# Sous-ensemble AnomalyGen
ag_mask = labels_upper.isin(ANOMALYGEN_SUPPORTED)
ag_df = df[ag_mask]
os.makedirs(os.path.dirname(anomalygen_gaps_parquet) or ".", exist_ok=True)
ag_df.to_parquet(anomalygen_gaps_parquet, index=False)

# Ventilation par label
summary_lines = [
    "Weak-sample routing summary",
    f"Total weak samples: {len(df)}",
    f"Mining subset:      {len(mn_df)} -> {mining_gaps_parquet}",
    f"AnomalyGen subset:  {len(ag_df)} -> {anomalygen_gaps_parquet}",
    "",
]
if pool_missing:
    summary_lines.append(f"No source pool CSV at {source_pool_csv!r}; mining subset is empty.")
    summary_lines.append("")
summary_lines.append("Per-label breakdown (count, mining, anomalygen):")
label_counts = labels_upper.value_counts()
for label, count in label_counts.items():
    in_mn = (not pool_missing) and label in pool_labels
    in_ag = label in ANOMALYGEN_SUPPORTED
    summary_lines.append(
        f"  {label}: {count} "
        f"(mining={'yes' if in_mn else 'no'}, "
        f"anomalygen={'yes' if in_ag else 'no'})"
    )
summary_text = "\n".join(summary_lines) + "\n"

os.makedirs(logs_dir, exist_ok=True)
with open(os.path.join(logs_dir, "routing_summary.txt"), "w", encoding="utf-8") as f:
    f.write(summary_text)
print(summary_text.strip())

Sorties

Écrivez tout dans un dossier horodaté. Le crochet d'empaquetage copiera automatiquement routing_config/ et claude_session.jsonl quand Routing_Report.md sera écrit.

<output_dir>/routing_results/YYYY-MM-DD_HHMMSS/
├── Routing_Report.md           # Rapport de routage complet
├── mining_gaps.parquet         # Sous-ensemble routé vers k-NN Mining
├── anomalygen_gaps.parquet     # Sous-ensemble routé vers AnomalyGen (Cosmos SDG)
├── routing_summary.txt         # Ventilation par label en texte brut
├── routing_config/             # Copié automatiquement par le crochet
└── claude_session.jsonl        # Copié automatiquement par le crochet

Au démarrage de l'exécution, obtenez l'horodatage réel en exécutant date +%Y-%m-%d_%H%M%S dans Bash. Si l'utilisateur spécifie un chemin de sortie personnalisé, utilisez-le directement mais conservez la disposition interne.


Structure du rapport

Gardez le rapport court (400–800 mots). Le routage est une décision déterministe ; la valeur réside dans le fait de rendre les décisions auditables, non narratives.

# VCN Routing Report: <Iteration / Experiment Name>

## 1. Verdict
- Total weak samples in: <N>
- Mining subset:     <N_mn> rows  →  `mining_gaps.parquet`
- AnomalyGen subset: <N_ag> rows  →  `anomalygen_gaps.parquet`
- Source pool present? <yes/no — and the path>
- One-line headline: "<X> labels routed, <Y> labels dropped (no module accepted)"

## 2. Inputs
| Input | Path | Notes |
|-------|------|-------|
| gaps_parquet     | … | rows=<N>, columns=<col list> |
| source_pool_csv  | … | rows=<M> or "not provided" / "missing" |

## 3. Per-Label Routing Decisions
| Label | Count in gaps | In source pool? | Mining? | AnomalyGen? | Routed To |
|-------|----------------|------------------|----------|--------------|-----------|

(One row per distinct label in `gaps_parquet`, uppercased. `Routed To` is one of:
`mining only`, `anomalygen only`, `mining+anomalygen`, `neither (DROPPED)`.
Use `neither (DROPPED)` whenever no module accepted the label. Sort by count descending.)

## 4. Module-Level Summaries
### 4.1 k-NN Mining
- Pool labels (from source_pool_csv): <list, or "pool missing">
- Labels accepted from input: <list>
- Total rows routed: <N_mn>
- Per-label row counts: <breakdown>

### 4.2 AnomalyGen (Cosmos SDG)
- Eligible labels (configured): PASS, EXCESS_SOLDER, MISSING, BRIDGE
- Labels accepted from input: <list>
- Total rows routed: <N_ag>
- Per-label row counts: <breakdown>

## 5. Dropped Labels (routed to NEITHER module)
| Label | Count | Why dropped | Suggested fix |
|-------|-------|-------------|----------------|

(Empty table is OK and means no labels were dropped. If non-empty, every row needs a
"why" — typically one of: "not in source pool AND not in AnomalyGen supported set",
"source pool missing entirely AND label not in AnomalyGen set", "label name doesn't
match any module's expected canonicalization".)

## 6. Recommended Actions
1. **If any labels are dropped**: seed the source pool with that label, OR extend
   `ANOMALYGEN_SUPPORTED_LABELS` (and the AnomalyGen generator coverage).
2. **If source pool is missing**: provide `source_pool_csv` to enable the Mining branch.
   Without it, half of the augmentation pipeline is dark.
3. **If AnomalyGen subset is empty**: gap analysis only surfaced labels AnomalyGen cannot
   generate; rely on Mining for this iteration, or extend the AnomalyGen integration.
4. **If both subsets are empty**: stop the SDA iteration. Nothing downstream can run.

Ordre d'exécution

  1. Exécutez date +%Y-%m-%d_%H%M%S pour obtenir l'horodatage ; créez <output_dir>/routing_results/<timestamp>/.
  2. Exécutez la recette Python (Étapes 1–4) pour produire mining_gaps.parquet, anomalygen_gaps.parquet et routing_summary.txt. Imprimez les statistiques de résumé sur stdout afin que le crochet de vérification de script puisse vérifier qu'il a été exécuté.
  3. Créez la table de décision par label en lisant les deux parquets et en calculant le verdict routé vers par label.
  4. Écrivez Routing_Report.md en dernier — l'écrire déclenche le crochet d'empaquetage, qui copie les journaux de session et la configuration de compétence à côté.

Skills similaires