Upload 4 files
Browse files- README.md +60 -0
- deepfaune-vit_large_patch14_dinov2.lvd142m.v2.pt +3 -0
- inference.py +365 -0
- taxonomy.csv +31 -0
README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-nc-sa-4.0
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
This model was trained by [the Deepfaune initiative](https://www.deepfaune.cnrs.fr/en/). The official location of the model files is [the Deepfaune GitLab repository](https://plmlab.math.cnrs.fr/deepfaune/software/-/tree/master).
|
| 6 |
+
|
| 7 |
+
These files are copied to this Hugging face repo because of the easy integration with [EcoAssist](https://addaxdatascience.com/ecoassist).
|
| 8 |
+
|
| 9 |
+
**Developer**
|
| 10 |
+
|
| 11 |
+
The DeepFaune initiative
|
| 12 |
+
|
| 13 |
+
**Model version**
|
| 14 |
+
|
| 15 |
+
v1.2
|
| 16 |
+
|
| 17 |
+
**Description**
|
| 18 |
+
|
| 19 |
+
The Deepfaune initiative aims at developing 'artificial intelligence' models to automatically classify species in images and videos collected using camera-traps. The initiative is led by a core academic team from the French 'Centre National de la Recherche Scientifique' (CNRS), in collaboration with more than 50 European partners involved in wildlife research, conservation and management. The Deepfaune models can be run through a custom software freely available on the website, or through other software packages or platforms like EcoAssist. New versions of the model are published regularly, increasing classification accuracy or adding new species to the list of species that can be recognized. More information is available at: https://www.deepfaune.cnrs.fr.
|
| 20 |
+
|
| 21 |
+
**Classes**
|
| 22 |
+
|
| 23 |
+
* badger
|
| 24 |
+
* ibex
|
| 25 |
+
* red deer
|
| 26 |
+
* chamois
|
| 27 |
+
* cat
|
| 28 |
+
* goat
|
| 29 |
+
* roe deer
|
| 30 |
+
* dog
|
| 31 |
+
* squirrel
|
| 32 |
+
* equid
|
| 33 |
+
* genet
|
| 34 |
+
* hedgehog
|
| 35 |
+
* lagomorph
|
| 36 |
+
* wolf
|
| 37 |
+
* lynx
|
| 38 |
+
* marmot
|
| 39 |
+
* micromammal
|
| 40 |
+
* mouflon
|
| 41 |
+
* sheep
|
| 42 |
+
* mustelid
|
| 43 |
+
* bird
|
| 44 |
+
* bear
|
| 45 |
+
* nutria
|
| 46 |
+
* fox
|
| 47 |
+
* wild boar
|
| 48 |
+
* cow
|
| 49 |
+
* beaver
|
| 50 |
+
* fallow deer
|
| 51 |
+
* otter
|
| 52 |
+
* raccoon
|
| 53 |
+
|
| 54 |
+
**Links**
|
| 55 |
+
|
| 56 |
+
[Learn more](https://www.deepfaune.cnrs.fr/en/)
|
| 57 |
+
|
| 58 |
+
[Cite](https://link.springer.com/article/10.1007/s10344-023-01742-7)
|
| 59 |
+
|
| 60 |
+
[License](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
deepfaune-vit_large_patch14_dinov2.lvd142m.v2.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2fca4c6b8d4c7661cba19fb185a454916d259749ac4e925a5903f0cb8a3ec463
|
| 3 |
+
size 1217730925
|
inference.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Inference script for EUR-DF-v1-2 (DeepFaune v1.2 European Wildlife Classifier)
|
| 3 |
+
|
| 4 |
+
The DeepFaune initiative develops AI models to automatically classify species in camera-trap
|
| 5 |
+
images and videos. Led by CNRS (France) in collaboration with 50+ European partners.
|
| 6 |
+
|
| 7 |
+
Model: DeepFaune v1.2
|
| 8 |
+
Input: 182x182 RGB images
|
| 9 |
+
Framework: PyTorch (Vision Transformer - DINOv2)
|
| 10 |
+
Classes: 30 European species and taxonomic groups
|
| 11 |
+
Developer: The DeepFaune initiative (CNRS)
|
| 12 |
+
Citation: https://doi.org/10.1007/s10344-023-01742-7
|
| 13 |
+
License: CC BY-SA 4.0
|
| 14 |
+
Info: https://www.deepfaune.cnrs.fr/en/
|
| 15 |
+
|
| 16 |
+
Author: Peter van Lunteren
|
| 17 |
+
Created: 2026-01-14
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from __future__ import annotations
|
| 21 |
+
|
| 22 |
+
import sys
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
|
| 25 |
+
import numpy as np
|
| 26 |
+
import timm
|
| 27 |
+
import torch
|
| 28 |
+
import torch.nn as nn
|
| 29 |
+
from PIL import Image, ImageFile
|
| 30 |
+
from torch import tensor
|
| 31 |
+
from torchvision.transforms import InterpolationMode, transforms
|
| 32 |
+
|
| 33 |
+
# Don't freak out over truncated images
|
| 34 |
+
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
| 35 |
+
|
| 36 |
+
# DeepFaune model constants
|
| 37 |
+
CROP_SIZE = 182
|
| 38 |
+
BACKBONE = "vit_large_patch14_dinov2.lvd142m"
|
| 39 |
+
|
| 40 |
+
# DeepFaune class names (English)
|
| 41 |
+
# Source: https://plmlab.math.cnrs.fr/deepfaune/software/-/blob/master/classifTools.py
|
| 42 |
+
CLASS_NAMES_EN = [
|
| 43 |
+
'badger', 'ibex', 'beaver', 'red deer', 'chamois', 'cat', 'goat',
|
| 44 |
+
'roe deer', 'dog', 'fallow deer', 'squirrel', 'equid', 'genet',
|
| 45 |
+
'hedgehog', 'lagomorph', 'wolf', 'otter', 'lynx', 'marmot',
|
| 46 |
+
'micromammal', 'mouflon', 'sheep', 'mustelid', 'bird', 'bear',
|
| 47 |
+
'nutria', 'raccoon', 'fox', 'wild boar', 'cow'
|
| 48 |
+
]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class DeepFauneModel(nn.Module):
|
| 52 |
+
"""
|
| 53 |
+
DeepFaune model wrapper.
|
| 54 |
+
|
| 55 |
+
Based on original DeepFaune classifTools.py Model class.
|
| 56 |
+
License: CeCILL (see header)
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
def __init__(self, model_path: Path):
|
| 60 |
+
"""Initialize DeepFaune ViT model."""
|
| 61 |
+
super().__init__()
|
| 62 |
+
self.model_path = model_path
|
| 63 |
+
self.backbone = BACKBONE
|
| 64 |
+
self.nbclasses = len(CLASS_NAMES_EN)
|
| 65 |
+
|
| 66 |
+
# Create timm model with ViT-Large DINOv2 backbone
|
| 67 |
+
self.base_model = timm.create_model(
|
| 68 |
+
BACKBONE,
|
| 69 |
+
pretrained=False,
|
| 70 |
+
num_classes=self.nbclasses,
|
| 71 |
+
dynamic_img_size=True
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
def forward(self, input):
|
| 75 |
+
"""Forward pass through model."""
|
| 76 |
+
return self.base_model(input)
|
| 77 |
+
|
| 78 |
+
def predict(self, data: torch.Tensor, device: torch.device) -> np.ndarray:
|
| 79 |
+
"""
|
| 80 |
+
Run prediction with softmax.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
data: Preprocessed image tensor
|
| 84 |
+
device: torch.device (cpu, cuda, or mps)
|
| 85 |
+
|
| 86 |
+
Returns:
|
| 87 |
+
Numpy array of softmax probabilities [num_classes]
|
| 88 |
+
"""
|
| 89 |
+
self.eval()
|
| 90 |
+
self.to(device)
|
| 91 |
+
|
| 92 |
+
with torch.no_grad():
|
| 93 |
+
x = data.to(device)
|
| 94 |
+
output = self.forward(x).softmax(dim=1)
|
| 95 |
+
return output.cpu().numpy()[0] # Return first (and only) batch item
|
| 96 |
+
|
| 97 |
+
def load_weights(self, device: torch.device) -> None:
|
| 98 |
+
"""
|
| 99 |
+
Load model weights from .pt file.
|
| 100 |
+
|
| 101 |
+
Based on original DeepFaune classifTools.py loadWeights method.
|
| 102 |
+
|
| 103 |
+
Args:
|
| 104 |
+
device: torch.device to load weights onto
|
| 105 |
+
|
| 106 |
+
Raises:
|
| 107 |
+
FileNotFoundError: If model file not found
|
| 108 |
+
RuntimeError: If loading fails
|
| 109 |
+
"""
|
| 110 |
+
if not self.model_path.exists():
|
| 111 |
+
raise FileNotFoundError(f"Model file not found: {self.model_path}")
|
| 112 |
+
|
| 113 |
+
try:
|
| 114 |
+
params = torch.load(self.model_path, map_location=device)
|
| 115 |
+
args = params['args']
|
| 116 |
+
|
| 117 |
+
# Validate number of classes matches
|
| 118 |
+
if self.nbclasses != args['num_classes']:
|
| 119 |
+
raise RuntimeError(
|
| 120 |
+
f"Model has {args['num_classes']} classes but expected {self.nbclasses}"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
self.backbone = args['backbone']
|
| 124 |
+
self.nbclasses = args['num_classes']
|
| 125 |
+
self.load_state_dict(params['state_dict'])
|
| 126 |
+
|
| 127 |
+
except Exception as e:
|
| 128 |
+
raise RuntimeError(f"Failed to load DeepFaune model weights: {e}") from e
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
class ModelInference:
|
| 132 |
+
"""DeepFaune v1.3 inference implementation for AddaxAI-WebUI."""
|
| 133 |
+
|
| 134 |
+
def __init__(self, model_dir: Path, model_path: Path):
|
| 135 |
+
"""
|
| 136 |
+
Initialize with model paths.
|
| 137 |
+
|
| 138 |
+
Args:
|
| 139 |
+
model_dir: Directory containing model files
|
| 140 |
+
model_path: Path to deepfaune-vit_large_patch14_dinov2.lvd142m.v3.pt file
|
| 141 |
+
"""
|
| 142 |
+
self.model_dir = model_dir
|
| 143 |
+
self.model_path = model_path
|
| 144 |
+
self.model: DeepFauneModel | None = None
|
| 145 |
+
self.device: torch.device | None = None
|
| 146 |
+
|
| 147 |
+
# DeepFaune preprocessing transforms
|
| 148 |
+
# Based on classifTools.py Classifier.__init__
|
| 149 |
+
self.transforms = transforms.Compose([
|
| 150 |
+
transforms.Resize(
|
| 151 |
+
size=(CROP_SIZE, CROP_SIZE),
|
| 152 |
+
interpolation=InterpolationMode.BICUBIC,
|
| 153 |
+
max_size=None,
|
| 154 |
+
antialias=None
|
| 155 |
+
),
|
| 156 |
+
transforms.ToTensor(),
|
| 157 |
+
transforms.Normalize(
|
| 158 |
+
mean=tensor([0.4850, 0.4560, 0.4060]),
|
| 159 |
+
std=tensor([0.2290, 0.2240, 0.2250])
|
| 160 |
+
)
|
| 161 |
+
])
|
| 162 |
+
|
| 163 |
+
def check_gpu(self) -> bool:
|
| 164 |
+
"""
|
| 165 |
+
Check GPU availability for DeepFaune (PyTorch).
|
| 166 |
+
|
| 167 |
+
Returns:
|
| 168 |
+
True if MPS (Apple Silicon) or CUDA available, False otherwise
|
| 169 |
+
"""
|
| 170 |
+
# Check Apple MPS (Apple Silicon)
|
| 171 |
+
try:
|
| 172 |
+
if torch.backends.mps.is_built() and torch.backends.mps.is_available():
|
| 173 |
+
return True
|
| 174 |
+
except Exception:
|
| 175 |
+
pass
|
| 176 |
+
|
| 177 |
+
# Check CUDA (NVIDIA)
|
| 178 |
+
return torch.cuda.is_available()
|
| 179 |
+
|
| 180 |
+
def load_model(self) -> None:
|
| 181 |
+
"""
|
| 182 |
+
Load DeepFaune model into memory.
|
| 183 |
+
|
| 184 |
+
This creates the ViT-Large DINOv2 model and loads the trained weights.
|
| 185 |
+
Model is stored in self.model and reused for all subsequent classifications.
|
| 186 |
+
|
| 187 |
+
Raises:
|
| 188 |
+
RuntimeError: If model loading fails
|
| 189 |
+
FileNotFoundError: If model_path is invalid
|
| 190 |
+
"""
|
| 191 |
+
# Determine device
|
| 192 |
+
if torch.cuda.is_available():
|
| 193 |
+
self.device = torch.device('cuda')
|
| 194 |
+
elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_built() and torch.backends.mps.is_available():
|
| 195 |
+
self.device = torch.device('mps')
|
| 196 |
+
else:
|
| 197 |
+
self.device = torch.device('cpu')
|
| 198 |
+
|
| 199 |
+
print(f"[DeepFaune] Loading model on device: {self.device}", file=sys.stderr, flush=True)
|
| 200 |
+
|
| 201 |
+
# Create and load model
|
| 202 |
+
self.model = DeepFauneModel(self.model_path)
|
| 203 |
+
self.model.load_weights(self.device)
|
| 204 |
+
|
| 205 |
+
print(
|
| 206 |
+
f"[DeepFaune] Model loaded: {BACKBONE} with {len(CLASS_NAMES_EN)} classes, "
|
| 207 |
+
f"resolution {CROP_SIZE}x{CROP_SIZE}",
|
| 208 |
+
file=sys.stderr, flush=True
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
def get_crop(
|
| 212 |
+
self, image: Image.Image, bbox: tuple[float, float, float, float]
|
| 213 |
+
) -> Image.Image:
|
| 214 |
+
"""
|
| 215 |
+
Crop image using DeepFaune preprocessing.
|
| 216 |
+
|
| 217 |
+
DeepFaune uses a squared crop approach:
|
| 218 |
+
1. Denormalize bbox coordinates
|
| 219 |
+
2. Square the crop (max of width/height)
|
| 220 |
+
3. Center the detection within the square
|
| 221 |
+
4. Clip to image boundaries
|
| 222 |
+
|
| 223 |
+
Based on classify_detections.py get_crop function.
|
| 224 |
+
|
| 225 |
+
Args:
|
| 226 |
+
image: Full-resolution PIL Image
|
| 227 |
+
bbox: Normalized bounding box (x, y, width, height) in range [0.0, 1.0]
|
| 228 |
+
|
| 229 |
+
Returns:
|
| 230 |
+
Cropped PIL Image ready for classification
|
| 231 |
+
|
| 232 |
+
Raises:
|
| 233 |
+
ValueError: If bbox is invalid
|
| 234 |
+
"""
|
| 235 |
+
width, height = image.size
|
| 236 |
+
|
| 237 |
+
# Denormalize bbox coordinates
|
| 238 |
+
xmin = int(round(bbox[0] * width))
|
| 239 |
+
ymin = int(round(bbox[1] * height))
|
| 240 |
+
xmax = int(round(bbox[2] * width)) + xmin
|
| 241 |
+
ymax = int(round(bbox[3] * height)) + ymin
|
| 242 |
+
|
| 243 |
+
xsize = xmax - xmin
|
| 244 |
+
ysize = ymax - ymin
|
| 245 |
+
|
| 246 |
+
if xsize <= 0 or ysize <= 0:
|
| 247 |
+
raise ValueError(f"Invalid bbox size: {xsize}x{ysize}")
|
| 248 |
+
|
| 249 |
+
# Square the crop by expanding smaller dimension
|
| 250 |
+
if xsize > ysize:
|
| 251 |
+
# Expand height to match width
|
| 252 |
+
expand = int((xsize - ysize) / 2)
|
| 253 |
+
ymin = ymin - expand
|
| 254 |
+
ymax = ymax + expand
|
| 255 |
+
elif ysize > xsize:
|
| 256 |
+
# Expand width to match height
|
| 257 |
+
expand = int((ysize - xsize) / 2)
|
| 258 |
+
xmin = xmin - expand
|
| 259 |
+
xmax = xmax + expand
|
| 260 |
+
|
| 261 |
+
# Clip to image boundaries
|
| 262 |
+
xmin_clipped = max(0, xmin)
|
| 263 |
+
ymin_clipped = max(0, ymin)
|
| 264 |
+
xmax_clipped = min(xmax, width)
|
| 265 |
+
ymax_clipped = min(ymax, height)
|
| 266 |
+
|
| 267 |
+
# Crop image
|
| 268 |
+
image_cropped = image.crop((xmin_clipped, ymin_clipped, xmax_clipped, ymax_clipped))
|
| 269 |
+
|
| 270 |
+
# Convert to RGB (DeepFaune requires RGB)
|
| 271 |
+
if image_cropped.mode != 'RGB':
|
| 272 |
+
image_cropped = image_cropped.convert('RGB')
|
| 273 |
+
|
| 274 |
+
return image_cropped
|
| 275 |
+
|
| 276 |
+
def get_classification(self, crop: Image.Image) -> list[list[str, float]]:
|
| 277 |
+
"""
|
| 278 |
+
Run DeepFaune classification on cropped image.
|
| 279 |
+
|
| 280 |
+
Workflow:
|
| 281 |
+
1. Preprocess crop with transforms (resize, normalize)
|
| 282 |
+
2. Run model prediction with softmax
|
| 283 |
+
3. Return all class probabilities (unsorted)
|
| 284 |
+
|
| 285 |
+
Args:
|
| 286 |
+
crop: Cropped PIL Image
|
| 287 |
+
|
| 288 |
+
Returns:
|
| 289 |
+
List of [class_name, confidence] lists for ALL classes.
|
| 290 |
+
Example: [["bison", 0.00001], ["badger", 0.00002], ["red deer", 0.99985], ...]
|
| 291 |
+
NOTE: Sorting by confidence is handled by classification_worker.py
|
| 292 |
+
|
| 293 |
+
Raises:
|
| 294 |
+
RuntimeError: If model not loaded or inference fails
|
| 295 |
+
"""
|
| 296 |
+
if self.model is None or self.device is None:
|
| 297 |
+
raise RuntimeError("Model not loaded - call load_model() first")
|
| 298 |
+
|
| 299 |
+
try:
|
| 300 |
+
# Preprocess image (resize + normalize)
|
| 301 |
+
tensor_cropped = self.transforms(crop).unsqueeze(dim=0) # Add batch dimension
|
| 302 |
+
|
| 303 |
+
# Run prediction
|
| 304 |
+
confs = self.model.predict(tensor_cropped, self.device)
|
| 305 |
+
|
| 306 |
+
# Build list of [class_name, confidence] pairs
|
| 307 |
+
classifications = []
|
| 308 |
+
for i, class_name in enumerate(CLASS_NAMES_EN):
|
| 309 |
+
confidence = float(confs[i])
|
| 310 |
+
classifications.append([class_name, confidence])
|
| 311 |
+
|
| 312 |
+
# NOTE: Sorting by confidence is handled by classification_worker.py
|
| 313 |
+
return classifications
|
| 314 |
+
|
| 315 |
+
except Exception as e:
|
| 316 |
+
raise RuntimeError(f"DeepFaune classification failed: {e}") from e
|
| 317 |
+
|
| 318 |
+
def get_class_names(self) -> dict[str, str]:
|
| 319 |
+
"""
|
| 320 |
+
Get mapping of class IDs to species names.
|
| 321 |
+
|
| 322 |
+
DeepFaune has 34 classes in a fixed order. We create a 1-indexed mapping
|
| 323 |
+
for JSON compatibility.
|
| 324 |
+
|
| 325 |
+
Returns:
|
| 326 |
+
Dict mapping class ID (1-indexed string) to species name
|
| 327 |
+
Example: {"1": "bison", "2": "badger", ..., "34": "cow"}
|
| 328 |
+
|
| 329 |
+
Raises:
|
| 330 |
+
RuntimeError: If model not loaded
|
| 331 |
+
"""
|
| 332 |
+
if self.model is None:
|
| 333 |
+
raise RuntimeError("Model not loaded - call load_model() first")
|
| 334 |
+
|
| 335 |
+
# Build 1-indexed mapping
|
| 336 |
+
class_names = {}
|
| 337 |
+
for i, class_name in enumerate(CLASS_NAMES_EN):
|
| 338 |
+
class_id_str = str(i + 1) # 1-indexed
|
| 339 |
+
class_names[class_id_str] = class_name
|
| 340 |
+
|
| 341 |
+
return class_names
|
| 342 |
+
|
| 343 |
+
def get_tensor(self, crop: Image.Image):
|
| 344 |
+
"""Preprocess a crop into a numpy array for batch inference."""
|
| 345 |
+
tensor_cropped = self.transforms(crop)
|
| 346 |
+
return tensor_cropped.numpy()
|
| 347 |
+
|
| 348 |
+
def classify_batch(self, batch):
|
| 349 |
+
"""Run inference on a batch of preprocessed numpy arrays."""
|
| 350 |
+
import torch
|
| 351 |
+
|
| 352 |
+
self.model.eval()
|
| 353 |
+
self.model.to(self.device)
|
| 354 |
+
tensor = torch.from_numpy(batch).to(self.device)
|
| 355 |
+
with torch.no_grad():
|
| 356 |
+
output = self.model.forward(tensor).softmax(dim=1).cpu().numpy()
|
| 357 |
+
|
| 358 |
+
results = []
|
| 359 |
+
for p in output:
|
| 360 |
+
classifications = [
|
| 361 |
+
[CLASS_NAMES_EN[i], float(p[i])]
|
| 362 |
+
for i in range(len(CLASS_NAMES_EN))
|
| 363 |
+
]
|
| 364 |
+
results.append(classifications)
|
| 365 |
+
return results
|
taxonomy.csv
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_class,class,order,family,genus,species
|
| 2 |
+
badger,mammalia,carnivora,mustelidae,meles,meles
|
| 3 |
+
ibex,mammalia,artiodactyla,bovidae,capra,ibex
|
| 4 |
+
beaver,mammalia,rodentia,castoridae,castor,fiber
|
| 5 |
+
red deer,mammalia,artiodactyla,cervidae,cervus,elaphus
|
| 6 |
+
chamois,mammalia,artiodactyla,bovidae,rupicapra,rupicapra
|
| 7 |
+
cat,mammalia,carnivora,felidae,felis,catus
|
| 8 |
+
goat,mammalia,artiodactyla,bovidae,capra,hircus
|
| 9 |
+
roe deer,mammalia,artiodactyla,cervidae,capreolus,capreolus
|
| 10 |
+
dog,mammalia,carnivora,canidae,canis,lupus familiaris
|
| 11 |
+
fallow deer,mammalia,artiodactyla,cervidae,dama,dama
|
| 12 |
+
squirrel,mammalia,rodentia,sciuridae,,
|
| 13 |
+
equid,mammalia,perissodactyla,equidae,,
|
| 14 |
+
genet,mammalia,carnivora,viverridae,genetta,genetta
|
| 15 |
+
hedgehog,mammalia,erinaceomorpha,erinaceidae,erinaceus,europaeus
|
| 16 |
+
lagomorph,mammalia,lagomorpha,,,
|
| 17 |
+
wolf,mammalia,carnivora,canidae,canis,lupus
|
| 18 |
+
otter,mammalia,carnivora,mustelidae,lutra,lutra
|
| 19 |
+
lynx,mammalia,carnivora,felidae,lynx,lynx
|
| 20 |
+
marmot,mammalia,rodentia,sciuridae,marmota,marmota
|
| 21 |
+
micromammal,mammalia,,,,
|
| 22 |
+
mouflon,mammalia,artiodactyla,bovidae,ovis,orientalis musimon
|
| 23 |
+
sheep,mammalia,artiodactyla,bovidae,ovis,aries
|
| 24 |
+
mustelid,mammalia,carnivora,mustelidae,,
|
| 25 |
+
bird,aves,,,,
|
| 26 |
+
bear,mammalia,carnivora,ursidae,ursus,arctos
|
| 27 |
+
nutria,mammalia,rodentia,myocastoridae,myocastor,coypus
|
| 28 |
+
raccoon,mammalia,carnivora,procyonidae,procyon,lotor
|
| 29 |
+
fox,mammalia,carnivora,canidae,vulpes,vulpes
|
| 30 |
+
wild boar,mammalia,artiodactyla,suidae,sus,scrofa
|
| 31 |
+
cow,mammalia,artiodactyla,bovidae,bos,taurus
|