megadetector 5.0.20__py3-none-any.whl → 5.0.22__py3-none-any.whl
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.
Potentially problematic release.
This version of megadetector might be problematic. Click here for more details.
- megadetector/data_management/cct_json_utils.py +143 -7
- megadetector/data_management/cct_to_md.py +12 -5
- megadetector/data_management/databases/integrity_check_json_db.py +83 -77
- megadetector/data_management/importers/osu-small-animals-to-json.py +4 -4
- megadetector/data_management/importers/raic_csv_to_md_results.py +416 -0
- megadetector/data_management/importers/zamba_results_to_md_results.py +1 -2
- megadetector/data_management/lila/create_lila_test_set.py +25 -11
- megadetector/data_management/lila/download_lila_subset.py +9 -2
- megadetector/data_management/lila/generate_lila_per_image_labels.py +3 -2
- megadetector/data_management/lila/test_lila_metadata_urls.py +5 -1
- megadetector/data_management/read_exif.py +10 -14
- megadetector/data_management/rename_images.py +1 -1
- megadetector/data_management/yolo_output_to_md_output.py +18 -5
- megadetector/detection/process_video.py +14 -3
- megadetector/detection/pytorch_detector.py +15 -3
- megadetector/detection/run_detector.py +4 -3
- megadetector/detection/run_inference_with_yolov5_val.py +121 -13
- megadetector/detection/video_utils.py +40 -17
- megadetector/postprocessing/classification_postprocessing.py +1 -1
- megadetector/postprocessing/combine_api_outputs.py +1 -1
- megadetector/postprocessing/compare_batch_results.py +931 -142
- megadetector/postprocessing/detector_calibration.py +565 -0
- megadetector/postprocessing/md_to_coco.py +85 -19
- megadetector/postprocessing/postprocess_batch_results.py +32 -21
- megadetector/postprocessing/validate_batch_results.py +174 -64
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +15 -12
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +1 -1
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +3 -1
- megadetector/utils/ct_utils.py +64 -2
- megadetector/utils/md_tests.py +15 -13
- megadetector/utils/path_utils.py +153 -37
- megadetector/utils/process_utils.py +9 -3
- megadetector/utils/write_html_image_list.py +21 -6
- megadetector/visualization/visualization_utils.py +329 -102
- megadetector/visualization/visualize_db.py +104 -63
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/LICENSE +0 -0
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/METADATA +143 -142
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/RECORD +40 -39
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/WHEEL +1 -1
- {megadetector-5.0.20.dist-info → megadetector-5.0.22.dist-info}/top_level.txt +0 -0
- megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py +0 -359
|
@@ -32,6 +32,9 @@ from megadetector.utils.write_html_image_list import write_html_image_list
|
|
|
32
32
|
from megadetector.data_management.cct_json_utils import IndexedJsonDb
|
|
33
33
|
from megadetector.visualization import visualization_utils as vis_utils
|
|
34
34
|
|
|
35
|
+
def isnan(x):
|
|
36
|
+
return (isinstance(x,float) and np.isnan(x))
|
|
37
|
+
|
|
35
38
|
|
|
36
39
|
#%% Settings
|
|
37
40
|
|
|
@@ -84,12 +87,12 @@ class DbVizOptions:
|
|
|
84
87
|
#: Number of pixels to expand each bounding box
|
|
85
88
|
self.box_expansion = 0
|
|
86
89
|
|
|
87
|
-
#: Only include images that contain annotations with these class names (not IDs)
|
|
90
|
+
#: Only include images that contain annotations with these class names (not IDs) (list)
|
|
88
91
|
#:
|
|
89
92
|
#: Mutually exclusive with classes_to_exclude
|
|
90
93
|
self.classes_to_include = None
|
|
91
94
|
|
|
92
|
-
#: Exclude images that contain annotations with these class names (not IDs)
|
|
95
|
+
#: Exclude images that contain annotations with these class names (not IDs) (list)
|
|
93
96
|
#:
|
|
94
97
|
#: Mutually exclusive with classes_to_include
|
|
95
98
|
self.classes_to_exclude = None
|
|
@@ -117,6 +120,12 @@ class DbVizOptions:
|
|
|
117
120
|
#: Should we show absolute (True) or relative (False) paths for each image?
|
|
118
121
|
self.show_full_paths = False
|
|
119
122
|
|
|
123
|
+
#: List of additional fields in the image struct that we should print in image headers
|
|
124
|
+
self.extra_image_fields_to_print = None
|
|
125
|
+
|
|
126
|
+
#: List of additional fields in the annotation struct that we should print in image headers
|
|
127
|
+
self.extra_annotation_fields_to_print = None
|
|
128
|
+
|
|
120
129
|
#: Set to False to skip existing images
|
|
121
130
|
self.force_rendering = True
|
|
122
131
|
|
|
@@ -164,6 +173,12 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
164
173
|
if options is None:
|
|
165
174
|
options = DbVizOptions()
|
|
166
175
|
|
|
176
|
+
# Consistency checking for fields with specific format requirements
|
|
177
|
+
|
|
178
|
+
# This should be a list, but if someone specifies a string, do a reasonable thing
|
|
179
|
+
if isinstance(options.extra_image_fields_to_print,str):
|
|
180
|
+
options.extra_image_fields_to_print = [options.extra_image_fields_to_print]
|
|
181
|
+
|
|
167
182
|
if not options.parallelize_rendering_with_threads:
|
|
168
183
|
print('Warning: process-based parallelization is not yet supported by visualize_db')
|
|
169
184
|
options.parallelize_rendering_with_threads = True
|
|
@@ -190,61 +205,69 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
190
205
|
annotations = image_db['annotations']
|
|
191
206
|
images = image_db['images']
|
|
192
207
|
categories = image_db['categories']
|
|
193
|
-
|
|
208
|
+
|
|
194
209
|
# Optionally remove all images without bounding boxes, *before* sampling
|
|
195
210
|
if options.trim_to_images_with_bboxes:
|
|
196
211
|
|
|
197
|
-
|
|
198
|
-
for
|
|
212
|
+
b_has_bbox = [False] * len(annotations)
|
|
213
|
+
for i_ann,ann in enumerate(annotations):
|
|
199
214
|
if 'bbox' in ann:
|
|
200
215
|
assert isinstance(ann['bbox'],list)
|
|
201
|
-
|
|
202
|
-
|
|
216
|
+
b_has_bbox[i_ann] = True
|
|
217
|
+
annotations_with_boxes = list(compress(annotations, b_has_bbox))
|
|
203
218
|
|
|
204
|
-
|
|
205
|
-
|
|
219
|
+
image_ids_with_boxes = [x['image_id'] for x in annotations_with_boxes]
|
|
220
|
+
image_ids_with_boxes = set(image_ids_with_boxes)
|
|
206
221
|
|
|
207
|
-
|
|
208
|
-
for
|
|
222
|
+
image_has_box = [False] * len(images)
|
|
223
|
+
for i_image,image in enumerate(images):
|
|
209
224
|
imageID = image['id']
|
|
210
|
-
if imageID in
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
images =
|
|
225
|
+
if imageID in image_ids_with_boxes:
|
|
226
|
+
image_has_box[i_image] = True
|
|
227
|
+
images_with_bboxes = list(compress(images, image_has_box))
|
|
228
|
+
images = images_with_bboxes
|
|
214
229
|
|
|
215
230
|
# Optionally include/remove images with specific labels, *before* sampling
|
|
216
231
|
|
|
217
232
|
assert (not ((options.classes_to_exclude is not None) and \
|
|
218
233
|
(options.classes_to_include is not None))), \
|
|
219
234
|
'Cannot specify an inclusion and exclusion list'
|
|
220
|
-
|
|
235
|
+
|
|
236
|
+
if options.classes_to_exclude is not None:
|
|
237
|
+
assert isinstance(options.classes_to_exclude,list), \
|
|
238
|
+
'If supplied, classes_to_exclude should be a list'
|
|
239
|
+
|
|
240
|
+
if options.classes_to_include is not None:
|
|
241
|
+
assert isinstance(options.classes_to_include,list), \
|
|
242
|
+
'If supplied, classes_to_include should be a list'
|
|
243
|
+
|
|
221
244
|
if (options.classes_to_exclude is not None) or (options.classes_to_include is not None):
|
|
222
245
|
|
|
223
246
|
print('Indexing database')
|
|
224
247
|
indexed_db = IndexedJsonDb(image_db)
|
|
225
|
-
|
|
226
|
-
for
|
|
248
|
+
b_valid_class = [True] * len(images)
|
|
249
|
+
for i_image,image in enumerate(images):
|
|
227
250
|
classes = indexed_db.get_classes_for_image(image)
|
|
228
251
|
if options.classes_to_exclude is not None:
|
|
229
|
-
for
|
|
230
|
-
if
|
|
231
|
-
|
|
252
|
+
for excluded_class in options.classes_to_exclude:
|
|
253
|
+
if excluded_class in classes:
|
|
254
|
+
b_valid_class[i_image] = False
|
|
232
255
|
break
|
|
233
256
|
elif options.classes_to_include is not None:
|
|
234
|
-
|
|
257
|
+
b_valid_class[i_image] = False
|
|
235
258
|
if options.multiple_categories_tag in options.classes_to_include:
|
|
236
259
|
if len(classes) > 1:
|
|
237
|
-
|
|
238
|
-
if not
|
|
260
|
+
b_valid_class[i_image] = True
|
|
261
|
+
if not b_valid_class[i_image]:
|
|
239
262
|
for c in classes:
|
|
240
263
|
if c in options.classes_to_include:
|
|
241
|
-
|
|
264
|
+
b_valid_class[i_image] = True
|
|
242
265
|
break
|
|
243
266
|
else:
|
|
244
267
|
raise ValueError('Illegal include/exclude combination')
|
|
245
268
|
|
|
246
|
-
|
|
247
|
-
images =
|
|
269
|
+
images_with_valid_classes = list(compress(images, b_valid_class))
|
|
270
|
+
images = images_with_valid_classes
|
|
248
271
|
|
|
249
272
|
# ...if we need to include/exclude categories
|
|
250
273
|
|
|
@@ -270,12 +293,12 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
270
293
|
|
|
271
294
|
# Set of dicts representing inputs to render_db_bounding_boxes:
|
|
272
295
|
#
|
|
273
|
-
# bboxes,
|
|
296
|
+
# bboxes, box_classes, image_path
|
|
274
297
|
rendering_info = []
|
|
275
298
|
|
|
276
299
|
print('Preparing rendering list')
|
|
277
300
|
|
|
278
|
-
for
|
|
301
|
+
for i_image,img in tqdm(df_img.iterrows(),total=len(df_img)):
|
|
279
302
|
|
|
280
303
|
img_id = img['id']
|
|
281
304
|
assert img_id is not None
|
|
@@ -291,17 +314,27 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
291
314
|
annos_i = df_anno.loc[df_anno['image_id'] == img_id, :] # all annotations on this image
|
|
292
315
|
|
|
293
316
|
bboxes = []
|
|
294
|
-
|
|
317
|
+
box_classes = []
|
|
295
318
|
|
|
296
319
|
# All the class labels we've seen for this image (with out without bboxes)
|
|
297
|
-
|
|
320
|
+
image_categories = set()
|
|
298
321
|
|
|
299
|
-
|
|
322
|
+
extra_annotation_field_string = ''
|
|
323
|
+
annotation_level_for_image = ''
|
|
300
324
|
|
|
301
325
|
# Iterate over annotations for this image
|
|
302
|
-
#
|
|
303
|
-
for
|
|
304
|
-
|
|
326
|
+
# i_ann = 0; anno = annos_i.iloc[i_ann]
|
|
327
|
+
for i_ann,anno in annos_i.iterrows():
|
|
328
|
+
|
|
329
|
+
if options.extra_annotation_fields_to_print is not None:
|
|
330
|
+
field_names = list(anno.index)
|
|
331
|
+
for field_name in field_names:
|
|
332
|
+
if field_name in options.extra_annotation_fields_to_print:
|
|
333
|
+
field_value = anno[field_name]
|
|
334
|
+
if (field_value is not None) and (not isnan(field_value)):
|
|
335
|
+
extra_annotation_field_string += ' ({}:{})'.format(
|
|
336
|
+
field_name,field_value)
|
|
337
|
+
|
|
305
338
|
if options.confidence_threshold is not None:
|
|
306
339
|
assert options.confidence_field_name in anno, \
|
|
307
340
|
'Error: confidence thresholding requested, ' + \
|
|
@@ -316,18 +349,18 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
316
349
|
annLevel = 'sequence'
|
|
317
350
|
else:
|
|
318
351
|
annLevel = 'image'
|
|
319
|
-
if
|
|
320
|
-
|
|
321
|
-
elif
|
|
322
|
-
|
|
352
|
+
if annotation_level_for_image == '':
|
|
353
|
+
annotation_level_for_image = annLevel
|
|
354
|
+
elif annotation_level_for_image != annLevel:
|
|
355
|
+
annotation_level_for_image = 'mixed'
|
|
323
356
|
|
|
324
|
-
|
|
325
|
-
|
|
357
|
+
category_id = anno['category_id']
|
|
358
|
+
category_name = label_map[category_id]
|
|
326
359
|
if options.add_search_links:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
360
|
+
category_name = category_name.replace('"','')
|
|
361
|
+
category_name = '<a href="https://www.google.com/search?tbm=isch&q={}">{}</a>'.format(
|
|
362
|
+
category_name,category_name)
|
|
363
|
+
image_categories.add(category_name)
|
|
331
364
|
|
|
332
365
|
if 'bbox' in anno:
|
|
333
366
|
bbox = anno['bbox']
|
|
@@ -335,11 +368,11 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
335
368
|
assert math.isnan(bbox), "I shouldn't see a bbox that's neither a box nor NaN"
|
|
336
369
|
continue
|
|
337
370
|
bboxes.append(bbox)
|
|
338
|
-
|
|
371
|
+
box_classes.append(anno['category_id'])
|
|
339
372
|
|
|
340
373
|
# ...for each of this image's annotations
|
|
341
374
|
|
|
342
|
-
|
|
375
|
+
image_classes = ', '.join(image_categories)
|
|
343
376
|
|
|
344
377
|
img_id_string = str(img_id).lower()
|
|
345
378
|
file_name = '{}_gt.jpg'.format(os.path.splitext(img_id_string)[0])
|
|
@@ -349,19 +382,19 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
349
382
|
for c in illegal_characters:
|
|
350
383
|
file_name = file_name.replace(c,'~')
|
|
351
384
|
|
|
352
|
-
rendering_info.append({'bboxes':bboxes, '
|
|
385
|
+
rendering_info.append({'bboxes':bboxes, 'box_classes':box_classes, 'img_path':img_path,
|
|
353
386
|
'output_file_name':file_name})
|
|
354
387
|
|
|
355
|
-
|
|
356
|
-
if len(
|
|
357
|
-
|
|
388
|
+
label_level_string = ''
|
|
389
|
+
if len(annotation_level_for_image) > 0:
|
|
390
|
+
label_level_string = ' (annotation level: {})'.format(annotation_level_for_image)
|
|
358
391
|
|
|
359
392
|
if 'frame_num' in img and 'seq_num_frames' in img:
|
|
360
|
-
|
|
393
|
+
frame_string = ' frame: {} of {},'.format(img['frame_num'],img['seq_num_frames'])
|
|
361
394
|
elif 'frame_num' in img:
|
|
362
|
-
|
|
395
|
+
frame_string = ' frame: {},'.format(img['frame_num'])
|
|
363
396
|
else:
|
|
364
|
-
|
|
397
|
+
frame_string = ''
|
|
365
398
|
|
|
366
399
|
if options.show_full_paths:
|
|
367
400
|
filename_text = img_path
|
|
@@ -370,22 +403,30 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
370
403
|
if options.include_filename_links:
|
|
371
404
|
filename_text = '<a href="{}">{}</a>'.format(img_path,filename_text)
|
|
372
405
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
def isnan(x):
|
|
376
|
-
return (isinstance(x,float) and np.isnan(x))
|
|
406
|
+
flag_string = ''
|
|
377
407
|
|
|
378
408
|
if ('flags' in img) and (not isnan(img['flags'])):
|
|
379
|
-
|
|
409
|
+
flag_string = ', flags: {}'.format(str(img['flags']))
|
|
380
410
|
|
|
411
|
+
extra_field_string = ''
|
|
412
|
+
|
|
413
|
+
if options.extra_image_fields_to_print is not None:
|
|
414
|
+
for field_name in options.extra_image_fields_to_print:
|
|
415
|
+
if field_name in img:
|
|
416
|
+
# Always include a leading comma; this either separates us from the
|
|
417
|
+
# previous field in [extra_fields_to_print] or from the rest of the string
|
|
418
|
+
extra_field_string += ', {}: {}'.format(
|
|
419
|
+
field_name,str(img[field_name]))
|
|
420
|
+
|
|
381
421
|
# We're adding html for an image before we render it, so it's possible this image will
|
|
382
422
|
# fail to render. For applications where this script is being used to debua a database
|
|
383
423
|
# (the common case?), this is useful behavior, for other applications, this is annoying.
|
|
384
424
|
image_dict = \
|
|
385
425
|
{
|
|
386
426
|
'filename': '{}/{}'.format('rendered_images', file_name),
|
|
387
|
-
'title': '{}<br/>{}, num boxes: {},
|
|
388
|
-
filename_text, img_id, len(bboxes),
|
|
427
|
+
'title': '{}<br/>{}, num boxes: {},{} class labels: {}{}{}{}{}'.format(
|
|
428
|
+
filename_text, img_id, len(bboxes), frame_string, image_classes,
|
|
429
|
+
label_level_string, flag_string, extra_field_string, extra_annotation_field_string),
|
|
389
430
|
'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;' + \
|
|
390
431
|
'text-align:left;margin-top:20;margin-bottom:5'
|
|
391
432
|
}
|
|
@@ -400,7 +441,7 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
400
441
|
|
|
401
442
|
img_path = rendering_info['img_path']
|
|
402
443
|
bboxes = rendering_info['bboxes']
|
|
403
|
-
|
|
444
|
+
bbox_classes = rendering_info['box_classes']
|
|
404
445
|
output_file_name = rendering_info['output_file_name']
|
|
405
446
|
output_full_path = os.path.join(output_dir, 'rendered_images', output_file_name)
|
|
406
447
|
|
|
@@ -426,7 +467,7 @@ def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
|
426
467
|
print('Image {} failed to open, error: {}'.format(img_path, e))
|
|
427
468
|
return False
|
|
428
469
|
|
|
429
|
-
vis_utils.render_db_bounding_boxes(boxes=bboxes, classes=
|
|
470
|
+
vis_utils.render_db_bounding_boxes(boxes=bboxes, classes=bbox_classes,
|
|
430
471
|
image=image, original_size=original_size,
|
|
431
472
|
label_map=label_map,
|
|
432
473
|
thickness=options.box_thickness,
|
|
File without changes
|
|
@@ -1,142 +1,143 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name: megadetector
|
|
3
|
-
Version: 5.0.
|
|
4
|
-
Summary: MegaDetector is an AI model that helps conservation folks spend less time doing boring things with camera trap images.
|
|
5
|
-
Author-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
6
|
-
Maintainer-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
7
|
-
License:
|
|
8
|
-
|
|
9
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
-
in the Software without restriction, including without limitation the rights
|
|
12
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
-
furnished to do so, subject to the following conditions:
|
|
15
|
-
|
|
16
|
-
The above copyright notice and this permission notice shall be included in all
|
|
17
|
-
copies or substantial portions of the Software.
|
|
18
|
-
|
|
19
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
-
SOFTWARE.
|
|
26
|
-
|
|
27
|
-
Project-URL: Homepage, https://github.com/agentmorris/MegaDetector
|
|
28
|
-
Project-URL: Documentation, https://megadetector.readthedocs.io
|
|
29
|
-
Project-URL: Bug Reports, https://github.com/agentmorris/MegaDetector/issues
|
|
30
|
-
Project-URL: Source, https://github.com/agentmorris/MegaDetector
|
|
31
|
-
Keywords: camera traps,conservation,wildlife,ai,megadetector
|
|
32
|
-
Classifier: Development Status :: 3 - Alpha
|
|
33
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
-
Classifier: Programming Language :: Python :: 3
|
|
35
|
-
Requires-Python: <3.12,>=3.9
|
|
36
|
-
Description-Content-Type: text/markdown
|
|
37
|
-
License-File: LICENSE
|
|
38
|
-
Requires-Dist: Pillow
|
|
39
|
-
Requires-Dist: tqdm
|
|
40
|
-
Requires-Dist: jsonpickle
|
|
41
|
-
Requires-Dist: humanfriendly
|
|
42
|
-
Requires-Dist: numpy
|
|
43
|
-
Requires-Dist: matplotlib
|
|
44
|
-
Requires-Dist: opencv-python
|
|
45
|
-
Requires-Dist: requests
|
|
46
|
-
Requires-Dist: pyqtree
|
|
47
|
-
Requires-Dist: seaborn
|
|
48
|
-
Requires-Dist: scikit-learn
|
|
49
|
-
Requires-Dist: pandas
|
|
50
|
-
Requires-Dist: PyYAML
|
|
51
|
-
Requires-Dist: ultralytics-yolov5
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
from megadetector.
|
|
94
|
-
from megadetector.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
import
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: megadetector
|
|
3
|
+
Version: 5.0.22
|
|
4
|
+
Summary: MegaDetector is an AI model that helps conservation folks spend less time doing boring things with camera trap images.
|
|
5
|
+
Author-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
6
|
+
Maintainer-email: Your friendly neighborhood MegaDetector team <cameratraps@lila.science>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Project-URL: Homepage, https://github.com/agentmorris/MegaDetector
|
|
28
|
+
Project-URL: Documentation, https://megadetector.readthedocs.io
|
|
29
|
+
Project-URL: Bug Reports, https://github.com/agentmorris/MegaDetector/issues
|
|
30
|
+
Project-URL: Source, https://github.com/agentmorris/MegaDetector
|
|
31
|
+
Keywords: camera traps,conservation,wildlife,ai,megadetector
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Requires-Python: <3.12,>=3.9
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Requires-Dist: Pillow>=9.5
|
|
39
|
+
Requires-Dist: tqdm>=4.64.0
|
|
40
|
+
Requires-Dist: jsonpickle>=3.0.2
|
|
41
|
+
Requires-Dist: humanfriendly>=10.0
|
|
42
|
+
Requires-Dist: numpy<1.24,>=1.22
|
|
43
|
+
Requires-Dist: matplotlib>=3.8.0
|
|
44
|
+
Requires-Dist: opencv-python>=4.8.0
|
|
45
|
+
Requires-Dist: requests>=2.31.0
|
|
46
|
+
Requires-Dist: pyqtree>=1.0.0
|
|
47
|
+
Requires-Dist: seaborn>=0.12.2
|
|
48
|
+
Requires-Dist: scikit-learn>=1.3.1
|
|
49
|
+
Requires-Dist: pandas>=2.1.1
|
|
50
|
+
Requires-Dist: PyYAML>=6.0.1
|
|
51
|
+
Requires-Dist: ultralytics-yolov5==0.1.1
|
|
52
|
+
Requires-Dist: python-dateutil
|
|
53
|
+
|
|
54
|
+
# MegaDetector
|
|
55
|
+
|
|
56
|
+
This package is a pip-installable version of the support/inference code for [MegaDetector](https://github.com/agentmorris/MegaDetector/?tab=readme-ov-file#megadetector), an object detection model that helps conservation biologists spend less time doing boring things with camera trap images. Complete documentation for this Python package is available at [megadetector.readthedocs.io](https://megadetector.readthedocs.io).
|
|
57
|
+
|
|
58
|
+
If you aren't looking for the Python package specifically, and you just want to learn more about what MegaDetector is all about, head over to the [MegaDetector repo](https://github.com/agentmorris/MegaDetector/?tab=readme-ov-file#megadetector).
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## Reasons you might not be looking for this package
|
|
62
|
+
|
|
63
|
+
### If you are an ecologist...
|
|
64
|
+
|
|
65
|
+
If you are an ecologist looking to use MegaDetector to help you get through your camera trap images, you probably don't want this package, or at least you probably don't want to start at this page. We recommend starting with our "[Getting started with MegaDetector](https://github.com/agentmorris/MegaDetector/blob/main/getting-started.md)" page, then digging in to the [MegaDetector User Guide](https://github.com/agentmorris/MegaDetector/blob/main/megadetector.md), which will walk you through the process of using MegaDetector.
|
|
66
|
+
|
|
67
|
+
### If you are a computer-vision-y type...
|
|
68
|
+
|
|
69
|
+
If you are a computer-vision-y person looking to run or fine-tune MegaDetector programmatically, you probably don't want this package. MegaDetector is just a fine-tuned version of [YOLOv5](https://github.com/ultralytics/yolov5), and the [ultralytics](https://github.com/ultralytics/ultralytics/) package (from the developers of YOLOv5) has a zillion bells and whistles for both inference and fine-tuning that this package doesn't.
|
|
70
|
+
|
|
71
|
+
## Reasons you might want to use this package
|
|
72
|
+
|
|
73
|
+
If you want to programmatically interact with the postprocessing tools from the MegaDetector repo, or programmatically run MegaDetector in a way that produces [Timelapse](https://saul.cpsc.ucalgary.ca/timelapse)-friendly output (i.e., output in the standard [MegaDetector output format](https://github.com/agentmorris/MegaDetector/tree/main/megadetector/api/batch_processing#megadetector-batch-output-format)), this package might be for you.
|
|
74
|
+
|
|
75
|
+
## If I haven't talked you out of using this package...
|
|
76
|
+
|
|
77
|
+
To install:
|
|
78
|
+
|
|
79
|
+
`pip install megadetector`
|
|
80
|
+
|
|
81
|
+
MegaDetector model weights aren't downloaded at pip-install time, but they will be (optionally) automatically downloaded the first time you run the model.
|
|
82
|
+
|
|
83
|
+
## Package reference
|
|
84
|
+
|
|
85
|
+
See [megadetector.readthedocs.io](https://megadetector.readthedocs.io).
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Examples of things you can do with this package
|
|
89
|
+
|
|
90
|
+
### Run MegaDetector on one image and count the number of detections
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
from megadetector.utils import url_utils
|
|
94
|
+
from megadetector.visualization import visualization_utils as vis_utils
|
|
95
|
+
from megadetector.detection import run_detector
|
|
96
|
+
|
|
97
|
+
# This is the image at the bottom of this page, it has one animal in it
|
|
98
|
+
image_url = 'https://github.com/agentmorris/MegaDetector/raw/main/images/orinoquia-thumb-web.jpg'
|
|
99
|
+
temporary_filename = url_utils.download_url(image_url)
|
|
100
|
+
|
|
101
|
+
image = vis_utils.load_image(temporary_filename)
|
|
102
|
+
|
|
103
|
+
# This will automatically download MDv5a; you can also specify a filename.
|
|
104
|
+
model = run_detector.load_detector('MDV5A')
|
|
105
|
+
|
|
106
|
+
result = model.generate_detections_one_image(image)
|
|
107
|
+
|
|
108
|
+
detections_above_threshold = [d for d in result['detections'] if d['conf'] > 0.2]
|
|
109
|
+
print('Found {} detections above threshold'.format(len(detections_above_threshold)))
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Run MegaDetector on a folder of images
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
from megadetector.detection.run_detector_batch import \
|
|
116
|
+
load_and_run_detector_batch, write_results_to_file
|
|
117
|
+
from megadetector.utils import path_utils
|
|
118
|
+
import os
|
|
119
|
+
|
|
120
|
+
# Pick a folder to run MD on recursively, and an output file
|
|
121
|
+
image_folder = os.path.expanduser('~/megadetector_test_images')
|
|
122
|
+
output_file = os.path.expanduser('~/megadetector_output_test.json')
|
|
123
|
+
|
|
124
|
+
# Recursively find images
|
|
125
|
+
image_file_names = path_utils.find_images(image_folder,recursive=True)
|
|
126
|
+
|
|
127
|
+
# This will automatically download MDv5a; you can also specify a filename.
|
|
128
|
+
results = load_and_run_detector_batch('MDV5A', image_file_names)
|
|
129
|
+
|
|
130
|
+
# Write results to a format that Timelapse and other downstream tools like.
|
|
131
|
+
write_results_to_file(results,
|
|
132
|
+
output_file,
|
|
133
|
+
relative_path_base=image_folder,
|
|
134
|
+
detector_file=detector_filename)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Contact
|
|
138
|
+
|
|
139
|
+
Contact <a href="cameratraps@lila.science">cameratraps@lila.science</a> with questions.
|
|
140
|
+
|
|
141
|
+
## Gratuitous animal picture
|
|
142
|
+
|
|
143
|
+
<img src="https://github.com/agentmorris/MegaDetector/raw/main/images/orinoquia-thumb-web_detections.jpg"><br/>Image credit University of Minnesota, from the [Orinoquía Camera Traps](http://lila.science/datasets/orinoquia-camera-traps/) data set.
|