ovicore 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ovicore-0.1.0/LICENSE +21 -0
- ovicore-0.1.0/MANIFEST.in +1 -0
- ovicore-0.1.0/PKG-INFO +316 -0
- ovicore-0.1.0/README.md +292 -0
- ovicore-0.1.0/ovicore/__init__.py +20 -0
- ovicore-0.1.0/ovicore/anotacoes.py +51 -0
- ovicore-0.1.0/ovicore/backends/__init__.py +1 -0
- ovicore-0.1.0/ovicore/backends/detectron2_backend.py +126 -0
- ovicore-0.1.0/ovicore/backends/huggingface_backend.py +69 -0
- ovicore-0.1.0/ovicore/backends/ultralytics_backend.py +85 -0
- ovicore-0.1.0/ovicore/contracts.py +250 -0
- ovicore-0.1.0/ovicore/fatiamento.py +121 -0
- ovicore-0.1.0/ovicore/io.py +241 -0
- ovicore-0.1.0/ovicore/metrics.py +133 -0
- ovicore-0.1.0/ovicore/relatorio.py +122 -0
- ovicore-0.1.0/ovicore.egg-info/PKG-INFO +316 -0
- ovicore-0.1.0/ovicore.egg-info/SOURCES.txt +20 -0
- ovicore-0.1.0/ovicore.egg-info/dependency_links.txt +1 -0
- ovicore-0.1.0/ovicore.egg-info/requires.txt +14 -0
- ovicore-0.1.0/ovicore.egg-info/top_level.txt +1 -0
- ovicore-0.1.0/pyproject.toml +39 -0
- ovicore-0.1.0/setup.cfg +4 -0
ovicore-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Ferreira Schopf
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
prune tests
|
ovicore-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ovicore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Avaliação padronizada e reprodutível de detectores de objetos treinados em diferentes frameworks
|
|
5
|
+
Author: Daniel Ferreira Schopf
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repositorio, https://github.com/UFSM-Fiocruz/hybrid-core
|
|
8
|
+
Keywords: object-detection,evaluation,reproducibility,detectron2,ultralytics,huggingface
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pandas>=2.2
|
|
13
|
+
Requires-Dist: numpy>=1.26
|
|
14
|
+
Provides-Extra: ultralytics
|
|
15
|
+
Requires-Dist: ultralytics>=8.3; extra == "ultralytics"
|
|
16
|
+
Requires-Dist: torch>=2.1; extra == "ultralytics"
|
|
17
|
+
Provides-Extra: sahi
|
|
18
|
+
Requires-Dist: sahi<0.13,>=0.11.19; extra == "sahi"
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
21
|
+
Requires-Dist: build; extra == "dev"
|
|
22
|
+
Requires-Dist: twine; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# ovicore
|
|
26
|
+
|
|
27
|
+
Biblioteca para avaliar e comparar modelos de detecção de ovos de *Aedes aegypti* em imagens de ovitrampas, independente do framework usado.
|
|
28
|
+
|
|
29
|
+
Projeto UFSM–Fiocruz. TCC em Sistemas de Informação, por Daniel Schopf, sob orientação da Profa. Andrea Schwertner Charão.
|
|
30
|
+
|
|
31
|
+
## O problema
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
## O que a biblioteca faz
|
|
38
|
+
|
|
39
|
+
O ovicore não muda a forma como cada framework treina. Ele parte de uma entrada única e padroniza o que sai.
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
┌─────────────────┐
|
|
43
|
+
ENTRADA ────► │ O FRAMEWORK │ ────► SAÍDA
|
|
44
|
+
(fonte única │ (não mexemos) │ (padronizada)
|
|
45
|
+
em COCO) │ │
|
|
46
|
+
└─────────────────┘
|
|
47
|
+
Detectron2
|
|
48
|
+
Ultralytics
|
|
49
|
+
HuggingFace
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
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.
|
|
53
|
+
|
|
54
|
+
## Instalação
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install ovicore
|
|
58
|
+
pip install "ovicore[sahi]"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
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`.
|
|
62
|
+
|
|
63
|
+
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`.
|
|
64
|
+
|
|
65
|
+
No Google Colab:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
%pip install -q ovicore
|
|
69
|
+
from ovicore.metrics import avaliar
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Para desenvolver:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/UFSM-Fiocruz/hybrid-core.git
|
|
76
|
+
cd hybrid-core
|
|
77
|
+
pip install -e ".[dev]"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
As dependências da biblioteca vêm do `pyproject.toml`. O `requirements.txt` continua com as versões fixas usadas no Colab.
|
|
81
|
+
|
|
82
|
+
O notebook [`notebooks/exemplo_avaliacao.ipynb`](notebooks/exemplo_avaliacao.ipynb) mostra o uso completo: instala, importa e avalia.
|
|
83
|
+
|
|
84
|
+
## Uso
|
|
85
|
+
|
|
86
|
+
### Calcular as métricas
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from ovicore.metrics import avaliar
|
|
90
|
+
|
|
91
|
+
resultado = avaliar(
|
|
92
|
+
pred_bboxes, pred_scores, pred_labels,
|
|
93
|
+
true_bboxes, true_labels,
|
|
94
|
+
score_threshold=0.40,
|
|
95
|
+
iou_threshold=0.45,
|
|
96
|
+
id2label={0: 'egg', 1: 'cluster'},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
for classe, m in resultado.items():
|
|
100
|
+
print(classe, m)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
geral {'vp': 229, 'fp': 163, 'fn': 157, 'precisao': 0.584, 'recall': 0.593, 'f1': 0.589}
|
|
105
|
+
egg {'vp': 219, 'fp': 156, 'fn': 150, 'precisao': 0.584, 'recall': 0.594, 'f1': 0.589}
|
|
106
|
+
cluster {'vp': 10, 'fp': 7, 'fn': 7, 'precisao': 0.588, 'recall': 0.588, 'f1': 0.588}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
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()`.
|
|
110
|
+
|
|
111
|
+
### Salvar os resultados
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from ovicore.io import montar_metadata, gravar_predicoes
|
|
115
|
+
|
|
116
|
+
metadata = montar_metadata(
|
|
117
|
+
framework="ultralytics", arquitetura="yolov8n",
|
|
118
|
+
dataset="coco-dataset-v2", fold="fold-01-416",
|
|
119
|
+
n_epochs=500, batch_size=16, learning_rate=0.01,
|
|
120
|
+
score_threshold=0.10, iou_threshold=0.45, device="cuda",
|
|
121
|
+
evaluation_score_threshold=0.40,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
csv_path, json_path = gravar_predicoes(
|
|
125
|
+
df, output_dir="saida", framework="ultralytics",
|
|
126
|
+
ambiente="colab", fold="fold-01-416", metadata=metadata,
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Gera dois arquivos:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
predictions-ultralytics-colab-fold-01-416.csv as predições
|
|
134
|
+
predictions-ultralytics-colab-fold-01-416.meta.json como foram geradas
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
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.
|
|
138
|
+
|
|
139
|
+
## Do notebook para a biblioteca
|
|
140
|
+
|
|
141
|
+
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:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from ovicore.anotacoes import ler_anotacoes_coco
|
|
145
|
+
from ovicore.contracts import SlicingConfig
|
|
146
|
+
from ovicore.fatiamento import carregar_modelo_sahi, inferir_com_sahi
|
|
147
|
+
from ovicore.io import montar_predicoes, gravar_predicoes
|
|
148
|
+
from ovicore.relatorio import resumo_execucoes
|
|
149
|
+
|
|
150
|
+
anotacoes = ler_anotacoes_coco("test/test.json")
|
|
151
|
+
modelo = carregar_modelo_sahi("ultralytics", "best.pt", None, score_threshold=0.10, device="cuda")
|
|
152
|
+
deteccoes = [inferir_com_sahi(modelo, f"test/{nome}", SlicingConfig()) for nome in anotacoes]
|
|
153
|
+
df = montar_predicoes(list(anotacoes), deteccoes, anotacoes)
|
|
154
|
+
gravar_predicoes(df, "saida", "ultralytics", "colab", "fold-01-416", metadata)
|
|
155
|
+
resumo_execucoes("saida")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
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)`.
|
|
159
|
+
|
|
160
|
+
Antes e depois, só na parte de inferência e avaliação:
|
|
161
|
+
|
|
162
|
+
| Modelo | Antes | Células de código | Linhas de código | Depois | Células de código | Linhas de código |
|
|
163
|
+
|---|---|---|---|---|---|---|
|
|
164
|
+
| Faster R-CNN | notebook do grupo | 22 | 371 | `avaliacao_faster_rcnn_detectron2.ipynb` | 8 | 66 |
|
|
165
|
+
| Mask R-CNN | notebook do grupo | 22 | 373 | `avaliacao_mask_rcnn_detectron2.ipynb` | 10 | 94 |
|
|
166
|
+
| DETR | notebook do grupo | 10 | 123 | `avaliacao_detr_huggingface.ipynb` | 9 | 97 |
|
|
167
|
+
| YOLOv8n | exportação `.py` do Untitled2 | sem registro | 173 | `avaliacao_yolov8n_ultralytics.ipynb` | 8 | 69 |
|
|
168
|
+
|
|
169
|
+
Como foi contado:
|
|
170
|
+
|
|
171
|
+
- Linhas de código são as linhas não vazias que não são comentário.
|
|
172
|
+
- 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.
|
|
173
|
+
- 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.
|
|
174
|
+
- 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.
|
|
175
|
+
- 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.
|
|
176
|
+
|
|
177
|
+
## Relatório de execuções
|
|
178
|
+
|
|
179
|
+
`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.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from ovicore.relatorio import resumo_execucoes, divergencias_de_configuracao
|
|
183
|
+
|
|
184
|
+
resumo = resumo_execucoes("saida")
|
|
185
|
+
print(resumo[["framework", "arquitetura", "n_epochs", "score_threshold", "f1"]])
|
|
186
|
+
print(divergencias_de_configuracao(resumo))
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
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.
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
framework arquitetura n_epochs score_threshold f1
|
|
193
|
+
detectron2 faster_rcnn_R_50_FPN_3x 100 0.5 0.5
|
|
194
|
+
ultralytics yolov8n 500 0.5 0.5
|
|
195
|
+
{'n_epochs': [100, 500], 'batch_size': [8, 16], 'learning_rate': [0.0001, 0.01]}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`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`](docs/ORCAMENTO.md).
|
|
199
|
+
|
|
200
|
+
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.
|
|
201
|
+
|
|
202
|
+
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`](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.
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
python scripts/relatorio_execucoes.py caminho/para/resultados
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Estrutura
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
hybrid-core/
|
|
212
|
+
├── ovicore/
|
|
213
|
+
│ ├── metrics.py IoU, comparação e métricas
|
|
214
|
+
│ ├── io.py gravação e leitura padronizadas
|
|
215
|
+
│ ├── relatorio.py resumo de execuções e escolha do limiar
|
|
216
|
+
│ ├── anotacoes.py verdade-terreno a partir do COCO
|
|
217
|
+
│ ├── fatiamento.py inferência com SAHI (extra [sahi])
|
|
218
|
+
│ ├── contracts.py tipos e interfaces do core
|
|
219
|
+
│ └── backends/ adaptadores por framework
|
|
220
|
+
├── tests/
|
|
221
|
+
│ ├── test_aceitacao.py metrics.py, io.py e contracts.py
|
|
222
|
+
│ ├── test_backend_*.py um por backend
|
|
223
|
+
│ ├── test_relatorio.py relatorio.py, com dados simulados
|
|
224
|
+
│ ├── test_anotacoes.py anotacoes.py, com um JSON pequeno
|
|
225
|
+
│ ├── test_fatiamento.py fatiamento.py, com um SAHI falso
|
|
226
|
+
│ ├── test_montar_predicoes.py
|
|
227
|
+
│ └── fixture_* dados reais usados nos testes
|
|
228
|
+
├── notebooks/
|
|
229
|
+
│ ├── exemplo_avaliacao.ipynb
|
|
230
|
+
│ ├── avaliacao_faster_rcnn_detectron2.ipynb
|
|
231
|
+
│ ├── avaliacao_mask_rcnn_detectron2.ipynb
|
|
232
|
+
│ ├── treino_detr_huggingface.ipynb notebook do framework, fora da biblioteca
|
|
233
|
+
│ ├── avaliacao_detr_huggingface.ipynb
|
|
234
|
+
│ └── avaliacao_yolov8n_ultralytics.ipynb
|
|
235
|
+
├── scripts/
|
|
236
|
+
│ └── relatorio_execucoes.py relatório dos quatro modelos a partir de resultados/
|
|
237
|
+
├── docs/
|
|
238
|
+
│ ├── EVIDENCIAS.md os problemas que motivaram cada regra
|
|
239
|
+
│ ├── CONTRATO.md formato do CSV e do JSON
|
|
240
|
+
│ ├── ORCAMENTO.md o que é orçamento de treino e o de cada modelo
|
|
241
|
+
│ └── RELATORIO_EXECUCOES.md gerado por scripts/relatorio_execucoes.py
|
|
242
|
+
├── .github/workflows/ testes a cada envio; tag v* publica no TestPyPI, Run workflow na tag publica no PyPI
|
|
243
|
+
├── LICENSE MIT
|
|
244
|
+
├── pyproject.toml
|
|
245
|
+
└── requirements.txt versões fixas para o Colab
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`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.
|
|
249
|
+
|
|
250
|
+
## Resultados obtidos
|
|
251
|
+
|
|
252
|
+
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`](docs/EVIDENCIAS.md)).
|
|
253
|
+
|
|
254
|
+
| | Faster R-CNN | Mask R-CNN | YOLOv8n |
|
|
255
|
+
|---|---|---|---|
|
|
256
|
+
| Framework | Detectron2 | Detectron2 | Ultralytics |
|
|
257
|
+
| 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 |
|
|
258
|
+
| Origem do limiar | 0,75 herdado do grupo | 0,75 herdado do grupo | 0,40 escolhido no teste com vazamento |
|
|
259
|
+
| Limiar escolhido na validação | 0,60 (F1 0,799 no teste) | n/a | n/a |
|
|
260
|
+
| Prevê a classe `cluster`? | Não | Sim | Sim |
|
|
261
|
+
| Tempo de instalação | ~10 min | ~10 min | 9 s |
|
|
262
|
+
|
|
263
|
+
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.
|
|
264
|
+
|
|
265
|
+
O Mask R-CNN tem duas medições com os mesmos pesos, no mesmo teste e no mesmo limiar:
|
|
266
|
+
|
|
267
|
+
| Mask R-CNN | VP | FP | FN | F1 |
|
|
268
|
+
|---|---|---|---|---|
|
|
269
|
+
| Resultado oficial, caixas da cabeça de detecção | 341 | 94 | 51 | 0,825 |
|
|
270
|
+
| Reprodução de 24/11/2024, caixas da máscara | 231 | 213 | 161 | 0,553 |
|
|
271
|
+
|
|
272
|
+
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.
|
|
273
|
+
|
|
274
|
+
O YOLOv8n, no teste limpo, com limiar 0,40, sem referência verificável:
|
|
275
|
+
|
|
276
|
+
| YOLOv8n | VP | FP | FN | F1 |
|
|
277
|
+
|---|---|---|---|---|
|
|
278
|
+
| Teste limpo, `fold01_416` antigo | 92 | 62 | 56 | 0,609 |
|
|
279
|
+
| Referência de 22/08/2026, teste com vazamento | 229 | 163 | 157 | 0,589 |
|
|
280
|
+
|
|
281
|
+
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.
|
|
282
|
+
|
|
283
|
+
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`](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.
|
|
284
|
+
|
|
285
|
+
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.
|
|
286
|
+
|
|
287
|
+
## De onde vêm as regras
|
|
288
|
+
|
|
289
|
+
As regras da biblioteca vieram de problemas encontrados ao rodar os frameworks. O arquivo [`docs/EVIDENCIAS.md`](docs/EVIDENCIAS.md) traz cada um com os números.
|
|
290
|
+
|
|
291
|
+
| Regra | Motivo |
|
|
292
|
+
|---|---|
|
|
293
|
+
| Coordenadas em `float` | `np.float32` corrompe o CSV sem dar erro na hora |
|
|
294
|
+
| 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 |
|
|
295
|
+
| Considerar a classe ao comparar | Sem isso, o problema da classe `cluster` fica invisível |
|
|
296
|
+
| Ordenar por confiança | Os notebooks comparam na ordem do arquivo |
|
|
297
|
+
| 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) |
|
|
298
|
+
| Salvar os metadados junto | O modelo salvo não bate com o que o notebook diz |
|
|
299
|
+
| 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) |
|
|
300
|
+
| Registrar a granularidade | Um dataset guarda a imagem inteira, outro guarda recortes |
|
|
301
|
+
|
|
302
|
+
## Testes
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
pytest -q
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
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.
|
|
309
|
+
|
|
310
|
+
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.
|
|
311
|
+
|
|
312
|
+
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.
|
|
313
|
+
|
|
314
|
+
## Licença
|
|
315
|
+
|
|
316
|
+
MIT. Ver [`LICENSE`](LICENSE).
|
ovicore-0.1.0/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# ovicore
|
|
2
|
+
|
|
3
|
+
Biblioteca para avaliar e comparar modelos de detecção de ovos de *Aedes aegypti* em imagens de ovitrampas, independente do framework usado.
|
|
4
|
+
|
|
5
|
+
Projeto UFSM–Fiocruz. TCC em Sistemas de Informação, por Daniel Schopf, sob orientação da Profa. Andrea Schwertner Charão.
|
|
6
|
+
|
|
7
|
+
## O problema
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
## O que a biblioteca faz
|
|
14
|
+
|
|
15
|
+
O ovicore não muda a forma como cada framework treina. Ele parte de uma entrada única e padroniza o que sai.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────┐
|
|
19
|
+
ENTRADA ────► │ O FRAMEWORK │ ────► SAÍDA
|
|
20
|
+
(fonte única │ (não mexemos) │ (padronizada)
|
|
21
|
+
em COCO) │ │
|
|
22
|
+
└─────────────────┘
|
|
23
|
+
Detectron2
|
|
24
|
+
Ultralytics
|
|
25
|
+
HuggingFace
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
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.
|
|
29
|
+
|
|
30
|
+
## Instalação
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install ovicore
|
|
34
|
+
pip install "ovicore[sahi]"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
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`.
|
|
38
|
+
|
|
39
|
+
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`.
|
|
40
|
+
|
|
41
|
+
No Google Colab:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
%pip install -q ovicore
|
|
45
|
+
from ovicore.metrics import avaliar
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Para desenvolver:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/UFSM-Fiocruz/hybrid-core.git
|
|
52
|
+
cd hybrid-core
|
|
53
|
+
pip install -e ".[dev]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
As dependências da biblioteca vêm do `pyproject.toml`. O `requirements.txt` continua com as versões fixas usadas no Colab.
|
|
57
|
+
|
|
58
|
+
O notebook [`notebooks/exemplo_avaliacao.ipynb`](notebooks/exemplo_avaliacao.ipynb) mostra o uso completo: instala, importa e avalia.
|
|
59
|
+
|
|
60
|
+
## Uso
|
|
61
|
+
|
|
62
|
+
### Calcular as métricas
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from ovicore.metrics import avaliar
|
|
66
|
+
|
|
67
|
+
resultado = avaliar(
|
|
68
|
+
pred_bboxes, pred_scores, pred_labels,
|
|
69
|
+
true_bboxes, true_labels,
|
|
70
|
+
score_threshold=0.40,
|
|
71
|
+
iou_threshold=0.45,
|
|
72
|
+
id2label={0: 'egg', 1: 'cluster'},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
for classe, m in resultado.items():
|
|
76
|
+
print(classe, m)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
geral {'vp': 229, 'fp': 163, 'fn': 157, 'precisao': 0.584, 'recall': 0.593, 'f1': 0.589}
|
|
81
|
+
egg {'vp': 219, 'fp': 156, 'fn': 150, 'precisao': 0.584, 'recall': 0.594, 'f1': 0.589}
|
|
82
|
+
cluster {'vp': 10, 'fp': 7, 'fn': 7, 'precisao': 0.588, 'recall': 0.588, 'f1': 0.588}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
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()`.
|
|
86
|
+
|
|
87
|
+
### Salvar os resultados
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from ovicore.io import montar_metadata, gravar_predicoes
|
|
91
|
+
|
|
92
|
+
metadata = montar_metadata(
|
|
93
|
+
framework="ultralytics", arquitetura="yolov8n",
|
|
94
|
+
dataset="coco-dataset-v2", fold="fold-01-416",
|
|
95
|
+
n_epochs=500, batch_size=16, learning_rate=0.01,
|
|
96
|
+
score_threshold=0.10, iou_threshold=0.45, device="cuda",
|
|
97
|
+
evaluation_score_threshold=0.40,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
csv_path, json_path = gravar_predicoes(
|
|
101
|
+
df, output_dir="saida", framework="ultralytics",
|
|
102
|
+
ambiente="colab", fold="fold-01-416", metadata=metadata,
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Gera dois arquivos:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
predictions-ultralytics-colab-fold-01-416.csv as predições
|
|
110
|
+
predictions-ultralytics-colab-fold-01-416.meta.json como foram geradas
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
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.
|
|
114
|
+
|
|
115
|
+
## Do notebook para a biblioteca
|
|
116
|
+
|
|
117
|
+
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:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from ovicore.anotacoes import ler_anotacoes_coco
|
|
121
|
+
from ovicore.contracts import SlicingConfig
|
|
122
|
+
from ovicore.fatiamento import carregar_modelo_sahi, inferir_com_sahi
|
|
123
|
+
from ovicore.io import montar_predicoes, gravar_predicoes
|
|
124
|
+
from ovicore.relatorio import resumo_execucoes
|
|
125
|
+
|
|
126
|
+
anotacoes = ler_anotacoes_coco("test/test.json")
|
|
127
|
+
modelo = carregar_modelo_sahi("ultralytics", "best.pt", None, score_threshold=0.10, device="cuda")
|
|
128
|
+
deteccoes = [inferir_com_sahi(modelo, f"test/{nome}", SlicingConfig()) for nome in anotacoes]
|
|
129
|
+
df = montar_predicoes(list(anotacoes), deteccoes, anotacoes)
|
|
130
|
+
gravar_predicoes(df, "saida", "ultralytics", "colab", "fold-01-416", metadata)
|
|
131
|
+
resumo_execucoes("saida")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
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)`.
|
|
135
|
+
|
|
136
|
+
Antes e depois, só na parte de inferência e avaliação:
|
|
137
|
+
|
|
138
|
+
| Modelo | Antes | Células de código | Linhas de código | Depois | Células de código | Linhas de código |
|
|
139
|
+
|---|---|---|---|---|---|---|
|
|
140
|
+
| Faster R-CNN | notebook do grupo | 22 | 371 | `avaliacao_faster_rcnn_detectron2.ipynb` | 8 | 66 |
|
|
141
|
+
| Mask R-CNN | notebook do grupo | 22 | 373 | `avaliacao_mask_rcnn_detectron2.ipynb` | 10 | 94 |
|
|
142
|
+
| DETR | notebook do grupo | 10 | 123 | `avaliacao_detr_huggingface.ipynb` | 9 | 97 |
|
|
143
|
+
| YOLOv8n | exportação `.py` do Untitled2 | sem registro | 173 | `avaliacao_yolov8n_ultralytics.ipynb` | 8 | 69 |
|
|
144
|
+
|
|
145
|
+
Como foi contado:
|
|
146
|
+
|
|
147
|
+
- Linhas de código são as linhas não vazias que não são comentário.
|
|
148
|
+
- 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.
|
|
149
|
+
- 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.
|
|
150
|
+
- 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.
|
|
151
|
+
- 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.
|
|
152
|
+
|
|
153
|
+
## Relatório de execuções
|
|
154
|
+
|
|
155
|
+
`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.
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from ovicore.relatorio import resumo_execucoes, divergencias_de_configuracao
|
|
159
|
+
|
|
160
|
+
resumo = resumo_execucoes("saida")
|
|
161
|
+
print(resumo[["framework", "arquitetura", "n_epochs", "score_threshold", "f1"]])
|
|
162
|
+
print(divergencias_de_configuracao(resumo))
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
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.
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
framework arquitetura n_epochs score_threshold f1
|
|
169
|
+
detectron2 faster_rcnn_R_50_FPN_3x 100 0.5 0.5
|
|
170
|
+
ultralytics yolov8n 500 0.5 0.5
|
|
171
|
+
{'n_epochs': [100, 500], 'batch_size': [8, 16], 'learning_rate': [0.0001, 0.01]}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`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`](docs/ORCAMENTO.md).
|
|
175
|
+
|
|
176
|
+
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.
|
|
177
|
+
|
|
178
|
+
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`](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.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
python scripts/relatorio_execucoes.py caminho/para/resultados
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Estrutura
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
hybrid-core/
|
|
188
|
+
├── ovicore/
|
|
189
|
+
│ ├── metrics.py IoU, comparação e métricas
|
|
190
|
+
│ ├── io.py gravação e leitura padronizadas
|
|
191
|
+
│ ├── relatorio.py resumo de execuções e escolha do limiar
|
|
192
|
+
│ ├── anotacoes.py verdade-terreno a partir do COCO
|
|
193
|
+
│ ├── fatiamento.py inferência com SAHI (extra [sahi])
|
|
194
|
+
│ ├── contracts.py tipos e interfaces do core
|
|
195
|
+
│ └── backends/ adaptadores por framework
|
|
196
|
+
├── tests/
|
|
197
|
+
│ ├── test_aceitacao.py metrics.py, io.py e contracts.py
|
|
198
|
+
│ ├── test_backend_*.py um por backend
|
|
199
|
+
│ ├── test_relatorio.py relatorio.py, com dados simulados
|
|
200
|
+
│ ├── test_anotacoes.py anotacoes.py, com um JSON pequeno
|
|
201
|
+
│ ├── test_fatiamento.py fatiamento.py, com um SAHI falso
|
|
202
|
+
│ ├── test_montar_predicoes.py
|
|
203
|
+
│ └── fixture_* dados reais usados nos testes
|
|
204
|
+
├── notebooks/
|
|
205
|
+
│ ├── exemplo_avaliacao.ipynb
|
|
206
|
+
│ ├── avaliacao_faster_rcnn_detectron2.ipynb
|
|
207
|
+
│ ├── avaliacao_mask_rcnn_detectron2.ipynb
|
|
208
|
+
│ ├── treino_detr_huggingface.ipynb notebook do framework, fora da biblioteca
|
|
209
|
+
│ ├── avaliacao_detr_huggingface.ipynb
|
|
210
|
+
│ └── avaliacao_yolov8n_ultralytics.ipynb
|
|
211
|
+
├── scripts/
|
|
212
|
+
│ └── relatorio_execucoes.py relatório dos quatro modelos a partir de resultados/
|
|
213
|
+
├── docs/
|
|
214
|
+
│ ├── EVIDENCIAS.md os problemas que motivaram cada regra
|
|
215
|
+
│ ├── CONTRATO.md formato do CSV e do JSON
|
|
216
|
+
│ ├── ORCAMENTO.md o que é orçamento de treino e o de cada modelo
|
|
217
|
+
│ └── RELATORIO_EXECUCOES.md gerado por scripts/relatorio_execucoes.py
|
|
218
|
+
├── .github/workflows/ testes a cada envio; tag v* publica no TestPyPI, Run workflow na tag publica no PyPI
|
|
219
|
+
├── LICENSE MIT
|
|
220
|
+
├── pyproject.toml
|
|
221
|
+
└── requirements.txt versões fixas para o Colab
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`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.
|
|
225
|
+
|
|
226
|
+
## Resultados obtidos
|
|
227
|
+
|
|
228
|
+
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`](docs/EVIDENCIAS.md)).
|
|
229
|
+
|
|
230
|
+
| | Faster R-CNN | Mask R-CNN | YOLOv8n |
|
|
231
|
+
|---|---|---|---|
|
|
232
|
+
| Framework | Detectron2 | Detectron2 | Ultralytics |
|
|
233
|
+
| 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 |
|
|
234
|
+
| Origem do limiar | 0,75 herdado do grupo | 0,75 herdado do grupo | 0,40 escolhido no teste com vazamento |
|
|
235
|
+
| Limiar escolhido na validação | 0,60 (F1 0,799 no teste) | n/a | n/a |
|
|
236
|
+
| Prevê a classe `cluster`? | Não | Sim | Sim |
|
|
237
|
+
| Tempo de instalação | ~10 min | ~10 min | 9 s |
|
|
238
|
+
|
|
239
|
+
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.
|
|
240
|
+
|
|
241
|
+
O Mask R-CNN tem duas medições com os mesmos pesos, no mesmo teste e no mesmo limiar:
|
|
242
|
+
|
|
243
|
+
| Mask R-CNN | VP | FP | FN | F1 |
|
|
244
|
+
|---|---|---|---|---|
|
|
245
|
+
| Resultado oficial, caixas da cabeça de detecção | 341 | 94 | 51 | 0,825 |
|
|
246
|
+
| Reprodução de 24/11/2024, caixas da máscara | 231 | 213 | 161 | 0,553 |
|
|
247
|
+
|
|
248
|
+
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.
|
|
249
|
+
|
|
250
|
+
O YOLOv8n, no teste limpo, com limiar 0,40, sem referência verificável:
|
|
251
|
+
|
|
252
|
+
| YOLOv8n | VP | FP | FN | F1 |
|
|
253
|
+
|---|---|---|---|---|
|
|
254
|
+
| Teste limpo, `fold01_416` antigo | 92 | 62 | 56 | 0,609 |
|
|
255
|
+
| Referência de 22/08/2026, teste com vazamento | 229 | 163 | 157 | 0,589 |
|
|
256
|
+
|
|
257
|
+
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.
|
|
258
|
+
|
|
259
|
+
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`](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.
|
|
260
|
+
|
|
261
|
+
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.
|
|
262
|
+
|
|
263
|
+
## De onde vêm as regras
|
|
264
|
+
|
|
265
|
+
As regras da biblioteca vieram de problemas encontrados ao rodar os frameworks. O arquivo [`docs/EVIDENCIAS.md`](docs/EVIDENCIAS.md) traz cada um com os números.
|
|
266
|
+
|
|
267
|
+
| Regra | Motivo |
|
|
268
|
+
|---|---|
|
|
269
|
+
| Coordenadas em `float` | `np.float32` corrompe o CSV sem dar erro na hora |
|
|
270
|
+
| 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 |
|
|
271
|
+
| Considerar a classe ao comparar | Sem isso, o problema da classe `cluster` fica invisível |
|
|
272
|
+
| Ordenar por confiança | Os notebooks comparam na ordem do arquivo |
|
|
273
|
+
| 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) |
|
|
274
|
+
| Salvar os metadados junto | O modelo salvo não bate com o que o notebook diz |
|
|
275
|
+
| 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) |
|
|
276
|
+
| Registrar a granularidade | Um dataset guarda a imagem inteira, outro guarda recortes |
|
|
277
|
+
|
|
278
|
+
## Testes
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
pytest -q
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
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.
|
|
285
|
+
|
|
286
|
+
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.
|
|
287
|
+
|
|
288
|
+
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.
|
|
289
|
+
|
|
290
|
+
## Licença
|
|
291
|
+
|
|
292
|
+
MIT. Ver [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Núcleo de avaliação padronizada para comparação de frameworks de detecção
|
|
3
|
+
de objetos.
|
|
4
|
+
|
|
5
|
+
Projeto UFSM-Fiocruz: detecção de ovos de Aedes aegypti em ovitrampas.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
from ovicore.metrics import avaliar, compute_iou, compute_metrics
|
|
11
|
+
from ovicore.io import (
|
|
12
|
+
gravar_predicoes, carregar_predicoes, montar_metadata,
|
|
13
|
+
validar_contrato, padronizar_labels_para_int,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"avaliar", "compute_iou", "compute_metrics",
|
|
18
|
+
"gravar_predicoes", "carregar_predicoes", "montar_metadata",
|
|
19
|
+
"validar_contrato", "padronizar_labels_para_int",
|
|
20
|
+
]
|