#!/usr/bin/env python """AIDA P0 — CLI de inferência. python predict.py imagem.jpg python predict.py imagem.jpg --json python predict.py *.jpg --json # uma linha JSON por imagem python predict.py imagem.jpg --models-dir /outro/models Códigos de saída: 0 todas as imagens foram analisadas 2 erro de entrada (arquivo, formato, imagem) 3 erro de ambiente (dependência, artefato, memória) Leia o MODEL_CARD.md antes de interpretar qualquer número. Resumo do que importa: o score é probabilístico, o FPR depende fortemente do tipo de fotografia, e o resultado não é prova de origem. """ from __future__ import annotations import argparse import json import sys from pathlib import Path def _raiz_do_pacote() -> Path: """Acha o diretorio que contem `aida_p0/`, sem assumir profundidade. O CLI e distribuido em dois arranjos: `release/predict.py` no repositorio de pesquisa e `scripts/predict.py` no pacote do Hugging Face. Um caminho fixo so funcionaria num dos dois. """ aqui = Path(__file__).resolve() for base in (aqui.parent, *aqui.parents): for candidato in (base, base / "src"): if (candidato / "aida_p0" / "__init__.py").exists(): return candidato raise SystemExit( "ERRO: nao encontrei o pacote `aida_p0`. Rode este script de dentro do " "pacote do AIDA (o diretorio que contem `aida_p0/` e `models/`).") sys.path.insert(0, str(_raiz_do_pacote())) from aida_p0 import AidaP0, erros # noqa: E402 AVISO = ("AIDA e um detector estatistico. O resultado NAO e prova de origem " "nem de fraude, e o FPR varia muito por dominio (ver MODEL_CARD).") def texto(r) -> str: marca = "!" if r.decisao_politica == "INCONCLUSIVO" else " " linhas = [ f"arquivo : {Path(r.arquivo).name}", f"score_ia : {r.score_ia:.6f}", f"classe : {r.classe}", f"threshold : {r.threshold!r}", f"decisao {marca} : {r.decisao_politica}", f"sensores : clip {r.score_clip:.6f} | forense {r.score_forense:.6f} " f"| rigid {r.score_rigid:.6f}", f"desacordo : {r.desacordo_entre_sensores:.6f} " f"qualidade: {r.qualidade:.4f}", f"tempo : {r.segundos:.2f} s", ] if r.motivos: linhas.append("motivos : " + "; ".join(r.motivos)) return "\n".join(linhas) def main(argv: list[str] | None = None) -> int: ap = argparse.ArgumentParser( description="AIDA P0 — deteccao de imagens geradas por IA", epilog=AVISO) ap.add_argument("imagens", nargs="+", help="caminho(s) de imagem") ap.add_argument("--json", action="store_true", help="uma linha JSON por imagem (JSONL)") ap.add_argument("--models-dir", default=None, help="diretorio dos artefatos (padrao: ./models)") ap.add_argument("--sem-verificar-hashes", action="store_true", help="pula a conferencia SHA-256 dos artefatos") ap.add_argument("--verbose", action="store_true") args = ap.parse_args(argv) try: aida = AidaP0(models_dir=args.models_dir, verificar_hashes=not args.sem_verificar_hashes, verbose=args.verbose) except erros.AidaError as e: print(f"ERRO: {e}", file=sys.stderr) return e.codigo_saida pior = 0 for i, caminho in enumerate(args.imagens): try: r = aida.analisar(caminho) except erros.AidaError as e: print(f"ERRO [{caminho}]: {e}", file=sys.stderr) pior = max(pior, e.codigo_saida) continue if args.json: print(json.dumps(r.to_dict(), ensure_ascii=False)) else: if i: print() print(texto(r)) if not args.json: print(f"\n{AVISO}", file=sys.stderr) return pior if __name__ == "__main__": raise SystemExit(main())