Addax-Data-Science commited on
Commit
1b09daa
·
verified ·
1 Parent(s): cba1d6d

Upload 4 files

Browse files
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