Skip to main content

ovicore

Biblioteca para avaliar e comparar modelos de detecção de ovos de Aedes aegypti em imagens de ovitrampas, independente do framework usado.

Projeto UFSM–Fiocruz. TCC em Sistemas de Informação, por Daniel Schopf, sob orientação da Profa. Andrea Schwertner Charão.

O problema

O projeto usa vários frameworks de detecção: Detectron2, Ultralytics e HuggingFace. Cada um treina do seu jeito, salva o resultado no seu formato e calcula as métricas com sua própria regra.

Isso torna a comparação entre eles pouco confiável. Quando dois números vêm de códigos diferentes, com convenções diferentes, comparar um com o outro não diz muita coisa.

O que a biblioteca faz

O ovicore não muda a forma como cada framework treina. Ele parte de uma entrada única e padroniza o que sai.

                    ┌─────────────────┐
   ENTRADA  ────►   │   O FRAMEWORK   │   ────►  SAÍDA
 (fonte única       │  (não mexemos)  │      (padronizada)
   em COCO)         │                 │
                    └─────────────────┘
                     Detectron2
                     Ultralytics
                     HuggingFace

A entrada é uma fonte única de anotações em COCO. Converter essa fonte para o formato de cada framework está fora desta versão e fica como trabalho futuro. Na saída, as predições de qualquer modelo passam pelo mesmo cálculo de métricas. A diferença entre dois resultados passa a ser a diferença entre os modelos, não entre os códigos que os mediram.

Instalação

pip install ovicore
pip install "ovicore[sahi]"

O segundo comando instala também o SAHI, para a inferência fatiada. A versão em desenvolvimento pode ser instalada direto do GitHub, com acesso ao repositório: pip install git+https://github.com/UFSM-Fiocruz/hybrid-core.git@biblioteca-pip.

Para ler o orçamento de checkpoints do Ultralytics, que exige torch, use o extra [ultralytics]. Para a inferência fatiada com SAHI, use o extra [sahi]. O resto da biblioteca só depende de pandas e numpy.

No Google Colab:

%pip install -q ovicore
from ovicore.metrics import avaliar

Para desenvolver:

git clone https://github.com/UFSM-Fiocruz/hybrid-core.git
cd hybrid-core
pip install -e ".[dev]"

As dependências da biblioteca vêm do pyproject.toml. O requirements.txt continua com as versões fixas usadas no Colab.

O notebook notebooks/exemplo_avaliacao.ipynb mostra o uso completo: instala, importa e avalia.

Uso

Calcular as métricas

from ovicore.metrics import avaliar

resultado = avaliar(
    pred_bboxes, pred_scores, pred_labels,
    true_bboxes, true_labels,
    score_threshold=0.40,
    iou_threshold=0.45,
    id2label={0: 'egg', 1: 'cluster'},
)

for classe, m in resultado.items():
    print(classe, m)
geral    {'vp': 229, 'fp': 163, 'fn': 157, 'precisao': 0.584, 'recall': 0.593, 'f1': 0.589}
egg      {'vp': 219, 'fp': 156, 'fn': 150, 'precisao': 0.584, 'recall': 0.594, 'f1': 0.589}
cluster  {'vp': 10,  'fp': 7,   'fn': 7,   'precisao': 0.588, 'recall': 0.588, 'f1': 0.588}

As caixas devem estar em [x1, y1, x2, y2]. Se o dataset usa o formato COCO ([x, y, largura, altura]), converta antes com BBox.from_coco().

Salvar os resultados

from ovicore.io import montar_metadata, gravar_predicoes

metadata = montar_metadata(
    framework="ultralytics", arquitetura="yolov8n",
    dataset="coco-dataset-v2", fold="fold-01-416",
    n_epochs=500, batch_size=16, learning_rate=0.01,
    score_threshold=0.10, iou_threshold=0.45, device="cuda",
    evaluation_score_threshold=0.40,
)

csv_path, json_path = gravar_predicoes(
    df, output_dir="saida", framework="ultralytics",
    ambiente="colab", fold="fold-01-416", metadata=metadata,
)

Gera dois arquivos:

predictions-ultralytics-colab-fold-01-416.csv        as predições
predictions-ultralytics-colab-fold-01-416.meta.json  como foram geradas

O JSON acompanha o CSV sempre. Quem recebe os dois consegue refazer o cálculo sem o notebook e sem adivinhar qual parâmetro foi usado.

Do notebook para a biblioteca

O que os notebooks repetem (verdade-terreno, SAHI e montagem do CSV) já está na biblioteca. Com o extra [sahi] instalado, o fluxo de avaliação de um modelo treinado fica assim, com metadata montado como na seção anterior:

from ovicore.anotacoes import ler_anotacoes_coco
from ovicore.contracts import SlicingConfig
from ovicore.fatiamento import carregar_modelo_sahi, inferir_com_sahi
from ovicore.io import montar_predicoes, gravar_predicoes
from ovicore.relatorio import resumo_execucoes

anotacoes = ler_anotacoes_coco("test/test.json")
modelo = carregar_modelo_sahi("ultralytics", "best.pt", None, score_threshold=0.10, device="cuda")
deteccoes = [inferir_com_sahi(modelo, f"test/{nome}", SlicingConfig()) for nome in anotacoes]
df = montar_predicoes(list(anotacoes), deteccoes, anotacoes)
gravar_predicoes(df, "saida", "ultralytics", "colab", "fold-01-416", metadata)
resumo_execucoes("saida")

A verdade-terreno sai em [x, y, x + w, y + h], sem o erro da caixa quadrada (E0). Os rótulos do modelo e os da anotação precisam estar no mesmo espaço de ids. Confira os ids antes de avaliar. No Faster R-CNN de referência, predições e test.json já usam 0 = egg e 1 = cluster, então o deslocamento é 0 (o padrão). Para reproduzir esses notebooks, use também SlicingConfig(class_agnostic=True).

Antes e depois, só na parte de inferência e avaliação:

Modelo Antes Células de código Linhas de código Depois Células de código Linhas de código
Faster R-CNN notebook do grupo 22 371 avaliacao_faster_rcnn_detectron2.ipynb 8 66
Mask R-CNN notebook do grupo 22 373 avaliacao_mask_rcnn_detectron2.ipynb 10 94
DETR notebook do grupo 10 123 avaliacao_detr_huggingface.ipynb 9 97
YOLOv8n exportação .py do Untitled2 sem registro 173 avaliacao_yolov8n_ultralytics.ipynb 8 69

Como foi contado:

  • Linhas de código são as linhas não vazias que não são comentário.
  • Nos notebooks do grupo, a parte de inferência e avaliação vai da seção "Avaliação" até o fim. Nos novos, vai da seção "3. Dados e modelo" até o fim, então inclui também a leitura da verdade-terreno, do orçamento e do modelo.
  • A exportação .py do YOLOv8n entra inteira, sem a instalação e sem as células de texto. Ela não guarda os limites das células, então o número de células fica sem registro.
  • No Mask R-CNN, o notebook novo faz duas avaliações: a reprodução, com as caixas da máscara, e o resultado oficial, com as caixas da cabeça de detecção.
  • No DETR, a diferença é menor por dois motivos. A avaliação do notebook do grupo não chega ao fim: a função compute_metrics nunca é chamada. E o notebook novo tem uma célula de 22 linhas que confere a escala dos scores (E10); sem ela, seriam 75 linhas.

Relatório de execuções

resumo_execucoes() lê todos os pares CSV + .meta.json de uma pasta e monta uma linha por execução, com a configuração e as métricas calculadas pelo mesmo código. CSV sem .meta.json fica de fora.

from ovicore.relatorio import resumo_execucoes, divergencias_de_configuracao

resumo = resumo_execucoes("saida")
print(resumo[["framework", "arquitetura", "n_epochs", "score_threshold", "f1"]])
print(divergencias_de_configuracao(resumo))

A saída abaixo vem de dados simulados, as duas execuções de demonstração do notebook de exemplo. Não é resultado de nenhum modelo real.

  framework             arquitetura  n_epochs  score_threshold  f1
 detectron2 faster_rcnn_R_50_FPN_3x       100              0.5 0.5
ultralytics                 yolov8n       500              0.5 0.5
{'n_epochs': [100, 500], 'batch_size': [8, 16], 'learning_rate': [0.0001, 0.01]}

divergencias_de_configuracao() lista os campos que variam entre as execuções. No exemplo, o F1 é igual, mas o orçamento de treino não, e isso fica registrado ao lado do resultado. O que conta como orçamento e os valores reais de cada modelo estão em docs/ORCAMENTO.md.

Para o limiar de confiança, varrer_limiares() calcula precisão, revocação e F1 em vários limiares, e escolher_limiar() escolhe o de maior F1 na partição de validação. O limiar escolhido é aplicado depois ao teste.

Para os quatro modelos do trabalho, scripts/relatorio_execucoes.py lê a pasta resultados/ baixada do Drive, roda resumo_execucoes() e divergencias_de_configuracao() sobre os CSVs de teste e grava docs/RELATORIO_EXECUCOES.md. A tabela mostra o dataset, o fold, a versao_dataset e o número de caixas de cada teste, porque cada modelo foi avaliado num conjunto diferente. Os CSVs de predições não entram no repositório, só o relatório.

python scripts/relatorio_execucoes.py caminho/para/resultados

Estrutura

hybrid-core/
├── ovicore/
│   ├── metrics.py      IoU, comparação e métricas
│   ├── io.py           gravação e leitura padronizadas
│   ├── relatorio.py    resumo de execuções e escolha do limiar
│   ├── anotacoes.py    verdade-terreno a partir do COCO
│   ├── fatiamento.py   inferência com SAHI (extra [sahi])
│   ├── contracts.py    tipos e interfaces do core
│   └── backends/       adaptadores por framework
├── tests/
│   ├── test_aceitacao.py       metrics.py, io.py e contracts.py
│   ├── test_backend_*.py       um por backend
│   ├── test_relatorio.py       relatorio.py, com dados simulados
│   ├── test_anotacoes.py       anotacoes.py, com um JSON pequeno
│   ├── test_fatiamento.py      fatiamento.py, com um SAHI falso
│   ├── test_montar_predicoes.py
│   └── fixture_*               dados reais usados nos testes
├── notebooks/
│   ├── exemplo_avaliacao.ipynb
│   ├── avaliacao_faster_rcnn_detectron2.ipynb
│   ├── avaliacao_mask_rcnn_detectron2.ipynb
│   ├── treino_detr_huggingface.ipynb       notebook do framework, fora da biblioteca
│   ├── avaliacao_detr_huggingface.ipynb
│   └── avaliacao_yolov8n_ultralytics.ipynb
├── scripts/
│   └── relatorio_execucoes.py   relatório dos quatro modelos a partir de resultados/
├── docs/
│   ├── EVIDENCIAS.md   os problemas que motivaram cada regra
│   ├── CONTRATO.md     formato do CSV e do JSON
│   ├── ORCAMENTO.md    o que é orçamento de treino e o de cada modelo
│   └── RELATORIO_EXECUCOES.md   gerado por scripts/relatorio_execucoes.py
├── .github/workflows/  testes a cada envio; tag v* publica no TestPyPI, Run workflow na tag publica no PyPI
├── LICENSE             MIT
├── pyproject.toml
└── requirements.txt    versões fixas para o Colab

metrics.py e io.py estão prontos e testados. Os backends estão no começo: Detectron2Backend, UltralyticsBackend e HuggingFaceBackend só implementam extrair_budget(). carregar_modelo() e inferir() levantam NotImplementedError. preparar_dataset() também, porque a conversão na entrada está fora do escopo desta versão.

Resultados obtidos

Três modelos avaliados com o mesmo módulo de avaliação, sem alterar código. O Faster R-CNN e o YOLOv8n usam as 6 imagens de teste do fold-01-416 atual, com recorte SAHI de 416px. Para o YOLOv8n esse teste tem vazamento: 4 das 6 imagens estavam no treino dele, e o F1 não é comparável. Por isso ele é avaliado no teste limpo do fold01_416 antigo, que tem só 2 imagens de origem (21 fatias, 148 caixas; ver E11). O Mask R-CNN foi treinado numa versão anterior do conjunto de dados e é avaliado no teste original dele, com sobreposição de 50% (ver E10 em docs/EVIDENCIAS.md).

Faster R-CNN Mask R-CNN YOLOv8n
Framework Detectron2 Detectron2 Ultralytics
F1 (limiar ajustado) 0,829 0,825 (limiar 0,75) 0,609 no teste limpo (limiar 0,40); o 0,589 da referência não é comparável: vazamento
Origem do limiar 0,75 herdado do grupo 0,75 herdado do grupo 0,40 escolhido no teste com vazamento
Limiar escolhido na validação 0,60 (F1 0,799 no teste) n/a n/a
Prevê a classe cluster? Não Sim Sim
Tempo de instalação ~10 min ~10 min 9 s

Só o limiar do Faster R-CNN foi escolhido na validação (37 fatias, 387 caixas): 0,60. No teste, ele dá VP 333, FP 115, FN 53 e F1 0,799, contra 0,829 com o 0,75 herdado. A validação é formada por fatias das mesmas imagens do treino, e o modelo vai melhor nela (F1 0,858 em 0,75, contra 0,829 no teste), o que provavelmente puxa o limiar para baixo. Nos outros modelos, o limiar é o 0,75 herdado do grupo (Mask R-CNN), o 0,40 escolhido no teste (YOLOv8n) e o 0,2 da demonstração (DETR). Ver E10.

O Mask R-CNN tem duas medições com os mesmos pesos, no mesmo teste e no mesmo limiar:

Mask R-CNN VP FP FN F1
Resultado oficial, caixas da cabeça de detecção 341 94 51 0,825
Reprodução de 24/11/2024, caixas da máscara 231 213 161 0,553

Só a origem das caixas muda entre as duas linhas: com a máscara ligada, o SAHI monta a caixa pelo contorno da máscara, em pixels inteiros, e isso baixa o F1 em 0,27. O resultado do Mask R-CNN não se compara diretamente com o do Faster R-CNN, porque foi medido em outro teste, sobre outra versão dos dados e com sobreposição de 0,5.

O YOLOv8n, no teste limpo, com limiar 0,40, sem referência verificável:

YOLOv8n VP FP FN F1
Teste limpo, fold01_416 antigo 92 62 56 0,609
Referência de 22/08/2026, teste com vazamento 229 163 157 0,589

As duas linhas não se comparam: a referência foi medida em outro teste, em que 4 das 6 imagens estavam no treino do modelo. O teste limpo tem só 2 imagens de origem, o que limita a variedade das imagens e dos aglomerados (6). O limiar de 0,40 foi escolhido no teste com vazamento e é provisório.

Os três modelos não foram treinados nas mesmas condições. As épocas, o learning rate e o otimizador são diferentes entre eles (ver docs/ORCAMENTO.md). Os números servem para verificar que a padronização funciona nos três, não para comparar a qualidade dos modelos. A biblioteca registra o orçamento de cada execução e aponta quando ele difere entre as execuções comparadas.

Um quarto framework, HuggingFace/DETR (conditional-detr-resnet-50), também passou pelo mesmo pipeline sem alterar código. Retreinado em 08/11/2026 no fold-01-256 e avaliado no teste dele (as mesmas 6 imagens do fold-01-416 atual), com limiar provisório de 0,2: VP 275, FP 1.733, FN 111, F1 0,230, sem referência verificável (ver E10). Não entra na tabela porque foi treinado num fold diferente dos outros três e com orçamento reduzido de propósito (30 épocas, só para validar o contrato). O F1 dele não é comparável com os de cima.

De onde vêm as regras

As regras da biblioteca vieram de problemas encontrados ao rodar os frameworks. O arquivo docs/EVIDENCIAS.md traz cada um com os números.

Regra Motivo
Coordenadas em float np.float32 corrompe o CSV sem dar erro na hora
Não truncar coordenadas Com a máscara, o SAHI devolve as caixas do Mask R-CNN em pixels inteiros, o que distorce a medição
Considerar a classe ao comparar Sem isso, o problema da classe cluster fica invisível
Ordenar por confiança Os notebooks comparam na ordem do arquivo
Ajustar o limiar por framework Com 0,75 fixo, o F1 do YOLOv8n cai de 0,589 para 0,234, no teste com vazamento (ver E11)
Salvar os metadados junto O modelo salvo não bate com o que o notebook diz
Registrar o treino usado Mask R-CNN, Faster R-CNN (referência do grupo, não o retreinado), DETR e YOLOv8n têm cerca de 183, cerca de 100, 30 e 45 épocas (o YOLOv8n de 500 configuradas, com parada antecipada)
Registrar a granularidade Um dataset guarda a imagem inteira, outro guarda recortes

Testes

pytest -q

Rodam localmente com pytest e no GitHub Actions a cada envio, em Python 3.10, 3.11 e 3.12 (.github/workflows/testes.yml). Nenhum precisa de GPU nem de framework de detecção.

Os dados são de dois tipos: simulados, montados dentro do próprio teste, e arquivos pequenos de exemplo em tests/ (um CSV real de predições do YOLOv8n, o metrics.json do Mask R-CNN e o train_args do YOLOv8n). Além de conferir os cálculos, os testes verificam se a biblioteca recusa os problemas conhecidos: tipos errados, rótulos em texto e arquivos sem metadados.

Treino e inferência não são testados aqui. Eles ficam nos notebooks de cada modelo. O único teste que abre um checkpoint .pt real é pulado quando faltam torch, ultralytics ou o próprio arquivo, que não é versionado.

Licença

MIT. Ver LICENSE.

Metadata

Release files for ovicore 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ovicore 0.1.0
File Size Uploaded Provenance Actions
ovicore-0.1.0.tar.gz 29.2 kB With provenance Details for ovicore-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ovicore 0.1.0
File Interpreter ABI Platform Actions
ovicore-0.1.0-py3-none-any.whl Python 3 none any Details for ovicore-0.1.0-py3-none-any.whl

Total release size: 56.8 kB

Release files / ovicore-0.1.0.tar.gz

Download URL ovicore-0.1.0.tar.gz
Size 29.2 kB
Tags Source
SHA-256 checksum
How to use checksums
34b4e34e52d3cd044d8e4c83c7cdbe6243ddf3c15e8c66e9478ca813aa2657d8
BLAKE2b-256 checksum
How to use checksums
32bb09f3546f5e0b60c97d6866c795911bdee0810fe7172fb220999f10e1c1c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Release files / ovicore-0.1.0-py3-none-any.whl

Download URL ovicore-0.1.0-py3-none-any.whl
Size 27.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c86a83fd4c2e31a01b3d3729d94eef1a3fa356a8fe6d574d52b429715e209c53
BLAKE2b-256 checksum
How to use checksums
d4d87e7ac80dcba00e32b934a4b9c36b29b587b7fc5b4c830dca78f48b03931f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Security for ovicore 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ovicore-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page