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
.pydo 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_metricsnunca é 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.