tao-route-visual-changenet-samples について
VCN AOI SDA パイプラインで、ギャップ分析後の弱サンプルを k-NN Mining と AnomalyGen に振り分けるワークフロースキルです。各モジュール向けの Parquet とラベル別サマリーを出力し、未対応ラベルも確認できます。
主な機能
- 01ソースプールのラベルで Mining 用データを抽出
- 02対応クラスで AnomalyGen 用データを抽出
- 03大文字と小文字を区別しない照合
- 04ラベル別ルーティングサマリーの作成
- 05どのモジュールにも該当しないラベルの警告
活用シーン
- 01ギャップ分析結果を拡張モジュールに振り分け
- 02ソースプールにないラベルを確認
- 03未ルーティングラベルを調べて対応範囲を調整
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ promptingcompany/ nv-skills --skill tao-route-visual-changenet-samplesインストール後、Claude Code で/tao-route-visual-changenet-samplesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
BENCHMARK.mdskill-card.mdskill.oms.sigevals/evals.jsonhooks/_parse-stdin.shrouting-artifacts-check.shrouting-coverage-check.shrouting-package.shrouting-script-check.shrouting-section-check.sh詳細
TAO VCN サンプルルーティングスキル
VCN AOI SDA パイプラインにおいて、ギャップ分析と拡張モジュールの間を取り持つディスパッチャーです。各拡張モジュールは、処理方法を把握しているラベルのみを扱えます。
- k-NN Mining が実際の画像から近傍サンプルをマイニングできるのは、ソースプール CSV にすでに存在するラベルに限られます。プールに
SHIFTの行がなければ、SHIFTの近傍サンプルを探しても意味がありません。 - AnomalyGen(Cosmos SDG)が合成異常を生成できるのは、推論パイプラインがサポートするクラス、すなわち
PASS、EXCESS_SOLDER、MISSING、BRIDGEのみです。このセットに含まれないラベルを持つ弱いサンプルは、AnomalyGen にはルーティングできません。
このスキルは、SDA の各イテレーションでギャップ分析の直後に1 回実行されます。ギャップ分析の parquet をモジュールごとにフィルタリングした parquet に分割し、各モジュールがそれぞれ処理可能なサブセットのみを扱えるようにします。また、ラベルごとのルーティング判断を人が読める形式でまとめた概要も出力します。
この処理は意図的に単純です。parquet を読み込み、.isin(...) フィルターを 2 回適用し、parquet を 2 つ書き出して、概要を 1 つ書き出します。このスキルの目的は、これらの判断を監査可能にすることです。モジュールが受け付けなかったためにラベルが気付かれずに除外されていないか、後続のレビュー担当者が確認できるよう、すべてのラベルについて各モジュールの可否を yes/no で概要に記載する必要があります。
入力
gaps_parquet— ギャップ分析の出力(通常はtao-analyze-gaps-visual-changenetの<exp_dir>/rca_results/<timestamp>/gaps.parquet)。必須列はfilepath、labelです。その他の列(siamese_score、weakness)はそのまま保持されます。source_pool_csv—label列を含む VCN 形式のマイニングソースプール CSV。空文字列または存在しないパスを指定できます。その場合、マイニング用サブセットは空になります。- 出力ディレクトリ — ルーティング済みの parquet 2 つ、概要、レポートを書き込む場所です。デフォルトは、ギャップ分析結果ディレクトリ配下のタイムスタンプ付きフォルダーです:
<rca_result_dir>/routing_results/<timestamp>/。 anomalygen_supported_labels(任意) — AnomalyGen で処理可能なラベルのデフォルトセットを上書きします。デフォルト:{"PASS", "EXCESS_SOLDER", "MISSING", "BRIDGE"}。**警告:**これはmdo-kratos-workflows/pipelines/sda/routing.pyのANOMALYGEN_SUPPORTED_LABELSおよび AnomalyGen 統合が実際に生成をサポートするクラスと常に一致させる必要があります。AnomalyGen に新しい欠陥クラスを追加する場合は、ここにも追加してください。
方法
このスキル全体では、大文字に変換したラベル列に対して .isin(...) マスクを 2 回適用します。
ステップ 1 — 読み込みと大文字化
df = pd.read_parquet(gaps_parquet)
labels_upper = df["label"].astype(str).str.upper()
両モジュールの判定では大文字と小文字を区別しません。出力 parquet では元の label 列を変更せずに保持し、比較キーのみを大文字に変換します。
ステップ 2 — マイニング用サブセット
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] # empty, but with the same schema
mn_df.to_parquet(mining_gaps_parquet, index=False)
プール CSV が存在しない、または空の場合、マイニング用サブセットは入力と同じ列を持つ空の DataFrame になります。これにより、スキーマの不一致で後続の読み込み処理が失敗するのを防ぎます。このケースは概要に記載してください。
ステップ 3 — 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)
ラベルが AnomalyGen のサポート対象セットに含まれる行は、そのまま anomalygen_gaps.parquet に書き込まれます。スキーマは入力 parquet と完全に一致するため、後続の AnomalyGen(Cosmos SDG)に変更を加える必要はありません。
ステップ 4 — ラベルごとのルーティング内訳
入力のギャップ parquet に含まれる一意なラベル(大文字化後)ごとに、次の情報を記録します。
count— このラベルを持つ行数mining— ラベルがpool_labelsに含まれる場合は yes、それ以外は noanomalygen— ラベルがANOMALYGEN_SUPPORTEDに含まれる場合は yes、それ以外は no
ラベルは両方のモジュールにルーティングされることがあります(例:PASS の行は AnomalyGen にルーティングされ、ソースプールにも PASS の行がある場合は Mining にもルーティングされます)。また、ラベルがどちらにもルーティングされない場合もあります。その場合は、気付かれずに除外されるため、設定の不一致を示している可能性があります。該当するラベルにはフラグを付けてください。
内訳は routing_summary.txt に書き込みます。フォーマットは参照コンポーネントと完全に同じです。
Weak-sample routing summary
Total weak samples: <N>
Mining subset: <N_mn> -> <mining_gaps_parquet>
AnomalyGen subset: <N_ag> -> <anomalygen_gaps_parquet>
[If pool missing:]
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)
...
ステップ 5 — 整合性チェック
両方のサブセットを書き込んだ後、次の点を確認します。
- サブセットのサイズの合計が
len(df)と等しくなる必要はありません。重複は許可されます(1 つのラベルが両方のモジュールに振り分けられる場合があります)。重要なのは、すべての入力行が少なくとも 1 つのサブセットに含まれるか、明示的な理由とともに「none」リストに含まれていることです。 len(mn_df) == 0かつlen(ag_df) == 0の場合は、何か問題があります。レポートで目立つように指摘してください。- ラベルグループ全体がどのモジュールにも振り分けられない場合は、ユーザーがそのラベルでソースプールをシードするか、AnomalyGen の対応ラベルを拡張できるよう、「Recommended Actions」セクションで明示してください。
Python レシピの参照
これは mdo-kratos-workflows/pipelines/sda/routing.py から取得した、正確な計算処理です。Bash から単一の Python スクリプトとして実行すると、レポート以外のすべての成果物が生成されます。
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()
# Mining subset
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)
# AnomalyGen subset
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)
# Per-label breakdown
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())
出力
すべてをタイムスタンプ付きフォルダーに書き込みます。Routing_Report.md が書き込まれると、パッケージングフックが routing_config/ と claude_session.jsonl を自動的にコピーします。
<output_dir>/routing_results/YYYY-MM-DD_HHMMSS/
├── Routing_Report.md # Full routing report
├── mining_gaps.parquet # Subset routed to k-NN Mining
├── anomalygen_gaps.parquet # Subset routed to AnomalyGen (Cosmos SDG)
├── routing_summary.txt # Plain-text per-label breakdown
├── routing_config/ # Auto-copied by hook
└── claude_session.jsonl # Auto-copied by hook
実行の開始時に、Bash で date +%Y-%m-%d_%H%M%S を実行して実際のタイムスタンプを取得します。ユーザーがカスタム出力パスを指定した場合は、そのパスを直接使用します。ただし、内部のレイアウトは維持してください。
レポートの構成
レポートは簡潔に(400~800 語)まとめてください。振り分けは決定論的な判断です。重要なのは物語のように説明することではなく、判断を監査可能にすることです。
# 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.
実行順序
date +%Y-%m-%d_%H%M%Sを実行してタイムスタンプを取得し、<output_dir>/routing_results/<timestamp>/を作成します。- Python レシピ(ステップ 1~4)を実行して、
mining_gaps.parquet、anomalygen_gaps.parquet、routing_summary.txtを生成します。スクリプトチェックフックで実行を検証できるよう、サマリー統計を標準出力に表示してください。 - 両方の Parquet を読み込み、ラベルごとに振り分け先の判定を計算して、ラベルごとの判断テーブルを作成します。
- 最後に
Routing_Report.mdを書き込みます。これによってパッケージングフックが作動し、セッションログとスキル設定が同じ場所にコピーされます。