megadetector 10.0.10__py3-none-any.whl → 10.0.11__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.

Files changed (80) hide show
  1. megadetector/data_management/animl_to_md.py +5 -2
  2. megadetector/data_management/cct_json_utils.py +4 -2
  3. megadetector/data_management/cct_to_md.py +5 -4
  4. megadetector/data_management/cct_to_wi.py +5 -1
  5. megadetector/data_management/coco_to_yolo.py +3 -2
  6. megadetector/data_management/databases/combine_coco_camera_traps_files.py +4 -4
  7. megadetector/data_management/databases/integrity_check_json_db.py +2 -2
  8. megadetector/data_management/databases/subset_json_db.py +0 -3
  9. megadetector/data_management/generate_crops_from_cct.py +6 -4
  10. megadetector/data_management/get_image_sizes.py +5 -35
  11. megadetector/data_management/labelme_to_coco.py +10 -6
  12. megadetector/data_management/labelme_to_yolo.py +19 -28
  13. megadetector/data_management/lila/create_lila_test_set.py +22 -2
  14. megadetector/data_management/lila/generate_lila_per_image_labels.py +7 -5
  15. megadetector/data_management/lila/lila_common.py +2 -2
  16. megadetector/data_management/lila/test_lila_metadata_urls.py +0 -1
  17. megadetector/data_management/ocr_tools.py +6 -10
  18. megadetector/data_management/read_exif.py +59 -16
  19. megadetector/data_management/remap_coco_categories.py +1 -1
  20. megadetector/data_management/remove_exif.py +10 -5
  21. megadetector/data_management/rename_images.py +20 -13
  22. megadetector/data_management/resize_coco_dataset.py +10 -4
  23. megadetector/data_management/speciesnet_to_md.py +3 -3
  24. megadetector/data_management/yolo_output_to_md_output.py +3 -1
  25. megadetector/data_management/yolo_to_coco.py +28 -19
  26. megadetector/detection/change_detection.py +26 -18
  27. megadetector/detection/process_video.py +1 -1
  28. megadetector/detection/pytorch_detector.py +5 -5
  29. megadetector/detection/run_detector.py +34 -10
  30. megadetector/detection/run_detector_batch.py +2 -1
  31. megadetector/detection/run_inference_with_yolov5_val.py +3 -1
  32. megadetector/detection/run_md_and_speciesnet.py +215 -101
  33. megadetector/detection/run_tiled_inference.py +7 -7
  34. megadetector/detection/tf_detector.py +1 -1
  35. megadetector/detection/video_utils.py +9 -6
  36. megadetector/postprocessing/add_max_conf.py +4 -4
  37. megadetector/postprocessing/categorize_detections_by_size.py +3 -2
  38. megadetector/postprocessing/classification_postprocessing.py +7 -8
  39. megadetector/postprocessing/combine_batch_outputs.py +3 -2
  40. megadetector/postprocessing/compare_batch_results.py +49 -27
  41. megadetector/postprocessing/convert_output_format.py +8 -6
  42. megadetector/postprocessing/create_crop_folder.py +13 -4
  43. megadetector/postprocessing/generate_csv_report.py +22 -8
  44. megadetector/postprocessing/load_api_results.py +8 -4
  45. megadetector/postprocessing/md_to_coco.py +2 -3
  46. megadetector/postprocessing/md_to_labelme.py +12 -8
  47. megadetector/postprocessing/md_to_wi.py +2 -1
  48. megadetector/postprocessing/merge_detections.py +4 -6
  49. megadetector/postprocessing/postprocess_batch_results.py +4 -3
  50. megadetector/postprocessing/remap_detection_categories.py +6 -3
  51. megadetector/postprocessing/render_detection_confusion_matrix.py +18 -10
  52. megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +1 -1
  53. megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +5 -3
  54. megadetector/postprocessing/separate_detections_into_folders.py +10 -4
  55. megadetector/postprocessing/subset_json_detector_output.py +1 -1
  56. megadetector/postprocessing/top_folders_to_bottom.py +22 -7
  57. megadetector/postprocessing/validate_batch_results.py +1 -1
  58. megadetector/taxonomy_mapping/map_new_lila_datasets.py +59 -3
  59. megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +1 -1
  60. megadetector/taxonomy_mapping/preview_lila_taxonomy.py +26 -17
  61. megadetector/taxonomy_mapping/species_lookup.py +51 -2
  62. megadetector/utils/ct_utils.py +9 -4
  63. megadetector/utils/extract_frames_from_video.py +4 -0
  64. megadetector/utils/gpu_test.py +6 -6
  65. megadetector/utils/md_tests.py +21 -21
  66. megadetector/utils/path_utils.py +112 -44
  67. megadetector/utils/split_locations_into_train_val.py +0 -4
  68. megadetector/utils/url_utils.py +5 -3
  69. megadetector/utils/wi_taxonomy_utils.py +37 -8
  70. megadetector/utils/write_html_image_list.py +1 -2
  71. megadetector/visualization/plot_utils.py +31 -19
  72. megadetector/visualization/render_images_with_thumbnails.py +3 -0
  73. megadetector/visualization/visualization_utils.py +18 -7
  74. megadetector/visualization/visualize_db.py +9 -26
  75. megadetector/visualization/visualize_video_output.py +14 -2
  76. {megadetector-10.0.10.dist-info → megadetector-10.0.11.dist-info}/METADATA +1 -1
  77. {megadetector-10.0.10.dist-info → megadetector-10.0.11.dist-info}/RECORD +80 -80
  78. {megadetector-10.0.10.dist-info → megadetector-10.0.11.dist-info}/WHEEL +0 -0
  79. {megadetector-10.0.10.dist-info → megadetector-10.0.11.dist-info}/licenses/LICENSE +0 -0
  80. {megadetector-10.0.10.dist-info → megadetector-10.0.11.dist-info}/top_level.txt +0 -0
@@ -25,6 +25,7 @@ from megadetector.utils.ct_utils import is_empty
25
25
  from megadetector.utils.ct_utils import sort_dictionary_by_value
26
26
  from megadetector.utils.ct_utils import sort_dictionary_by_key
27
27
  from megadetector.utils.ct_utils import invert_dictionary
28
+ from megadetector.utils.ct_utils import write_json
28
29
 
29
30
  from megadetector.utils.wi_taxonomy_utils import clean_taxonomy_string
30
31
  from megadetector.utils.wi_taxonomy_utils import taxonomy_level_index
@@ -420,7 +421,7 @@ def _smooth_classifications_for_list_of_detections(detections,
420
421
 
421
422
  if verbose_debug_enabled:
422
423
  _print_counts_with_names(category_to_count,classification_descriptions)
423
- from IPython import embed; embed()
424
+ # from IPython import embed; embed()
424
425
 
425
426
 
426
427
  ## Possibly change "other" classifications to the most common category
@@ -448,7 +449,7 @@ def _smooth_classifications_for_list_of_detections(detections,
448
449
  if verbose_debug_enabled:
449
450
  print('Replacing {} with {}'.format(
450
451
  classification_descriptions[c[0]],
451
- classification_descriptions[c[1]]))
452
+ most_common_category))
452
453
 
453
454
  n_other_classifications_changed_this_image += 1
454
455
  c[0] = most_common_category
@@ -918,8 +919,7 @@ def smooth_classification_results_image_level(input_file,output_file=None,option
918
919
 
919
920
  if output_file is not None:
920
921
  print('Writing results after image-level smoothing to:\n{}'.format(output_file))
921
- with open(output_file,'w') as f:
922
- json.dump(d,f,indent=1)
922
+ write_json(output_file,d)
923
923
 
924
924
  return d
925
925
 
@@ -1092,8 +1092,7 @@ def smooth_classification_results_sequence_level(input_file,
1092
1092
  if output_file is not None:
1093
1093
  print('Writing sequence-smoothed classification results to {}'.format(
1094
1094
  output_file))
1095
- with open(output_file,'w') as f:
1096
- json.dump(d,f,indent=1)
1095
+ write_json(output_file,d)
1097
1096
 
1098
1097
  return d
1099
1098
 
@@ -1681,7 +1680,7 @@ def restrict_to_taxa_list(taxa_list,
1681
1680
 
1682
1681
  ##%% Write output
1683
1682
 
1684
- with open(output_file,'w') as f:
1685
- json.dump(output_data,f,indent=1)
1683
+ write_json(output_file,output_data)
1684
+
1686
1685
 
1687
1686
  # ...def restrict_to_taxa_list(...)
@@ -203,7 +203,8 @@ def combine_api_shard_files(input_files, output_file=None):
203
203
  input_lists = []
204
204
  print('Loading input files')
205
205
  for fn in input_files:
206
- input_lists.append(json.load(open(fn)))
206
+ with open(fn,'r') as f:
207
+ input_lists.append(json.load(f))
207
208
 
208
209
  detections = []
209
210
  # detection_list = input_lists[0]
@@ -214,7 +215,7 @@ def combine_api_shard_files(input_files, output_file=None):
214
215
  assert 'file' in d
215
216
  assert 'max_detection_conf' in d
216
217
  assert 'detections' in d
217
- detections.extend([d])
218
+ detections.append(d)
218
219
 
219
220
  print('Writing output')
220
221
  if output_file is not None:
@@ -353,10 +353,11 @@ def _render_image_pair(fn,image_pairs,category_folder,options,pairwise_options):
353
353
  im_gt = image_pair['im_gt']
354
354
  annotations_gt = image_pair['annotations_gt']
355
355
  gt_boxes = []
356
+ gt_categories = []
356
357
  for ann in annotations_gt:
357
358
  if 'bbox' in ann:
358
359
  gt_boxes.append(ann['bbox'])
359
- gt_categories = [ann['category_id'] for ann in annotations_gt]
360
+ gt_categories.append(ann['category_id'])
360
361
 
361
362
  if len(gt_boxes) > 0:
362
363
 
@@ -474,7 +475,7 @@ def _result_types_to_comparison_category(result_types_present_a,
474
475
  ('tp' not in result_types_present_b):
475
476
  return 'clean_tp_a_only'
476
477
  # Otherwise, TPs are cases where one model has only TPs, and the other model
477
- # has any mistakse
478
+ # has any mistakes
478
479
  if ('fn' in result_types_present_b) or ('fp' in result_types_present_b):
479
480
  return 'tp_a_only'
480
481
 
@@ -486,7 +487,7 @@ def _result_types_to_comparison_category(result_types_present_a,
486
487
  ('tp' not in result_types_present_a):
487
488
  return 'clean_tp_b_only'
488
489
  # Otherwise, TPs are cases where one model has only TPs, and the other model
489
- # has any mistakse
490
+ # has any mistakes
490
491
  if ('fn' in result_types_present_a) or ('fp' in result_types_present_a):
491
492
  return 'tp_b_only'
492
493
 
@@ -674,11 +675,17 @@ def _pairwise_compare_batch_results(options,output_index,pairwise_options):
674
675
  category_ids_to_include_a = []
675
676
  category_ids_to_include_b = []
676
677
 
677
- for category_name in options.category_names_to_include:
678
- if category_name in category_name_to_id_a:
679
- category_ids_to_include_a.append(category_name_to_id_a[category_name])
680
- if category_name in category_name_to_id_b:
681
- category_ids_to_include_b.append(category_name_to_id_b[category_name])
678
+ # If we're supposed to be including all categories, we don't actually need to
679
+ # populate category_ids_to_include_a/b, but we're doing this for future-proofing.
680
+ if options.category_names_to_include is None:
681
+ category_ids_to_include_a = sorted(list(category_name_to_id_a.values()))
682
+ category_ids_to_include_b = sorted(list(category_name_to_id_b.values()))
683
+ else:
684
+ for category_name in options.category_names_to_include:
685
+ if category_name in category_name_to_id_a:
686
+ category_ids_to_include_a.append(category_name_to_id_a[category_name])
687
+ if category_name in category_name_to_id_b:
688
+ category_ids_to_include_b.append(category_name_to_id_b[category_name])
682
689
 
683
690
  if pairwise_options.results_description_a is None:
684
691
  if 'detector' not in results_a['info']:
@@ -814,7 +821,7 @@ def _pairwise_compare_batch_results(options,output_index,pairwise_options):
814
821
  print('Warning: {} files are only available in the ground truth (not in MD results)'.format(
815
822
  len(filenames_only_in_gt)))
816
823
 
817
- filenames_only_in_results = gt_filenames_set.difference(gt_filenames)
824
+ filenames_only_in_results = filenames_to_compare_set.difference(gt_filenames_set)
818
825
  if len(filenames_only_in_results) > 0:
819
826
  print('Warning: {} files are only available in the MD results (not in ground truth)'.format(
820
827
  len(filenames_only_in_results)))
@@ -1185,13 +1192,6 @@ def _pairwise_compare_batch_results(options,output_index,pairwise_options):
1185
1192
  if ('tp' in result_types_present_b) or ('fn' in result_types_present_b):
1186
1193
  assert 'tp' in result_types_present_a or 'fn' in result_types_present_a
1187
1194
 
1188
- # If either model has a TP or FN, the other has to have a TP or FN, since
1189
- # there was something in the GT
1190
- if ('tp' in result_types_present_a) or ('fn' in result_types_present_a):
1191
- assert 'tp' in result_types_present_b or 'fn' in result_types_present_b
1192
- if ('tp' in result_types_present_b) or ('fn' in result_types_present_b):
1193
- assert 'tp' in result_types_present_a or 'fn' in result_types_present_a
1194
-
1195
1195
 
1196
1196
  ## Choose a comparison category based on result types
1197
1197
 
@@ -1677,8 +1677,8 @@ def n_way_comparison(filenames,
1677
1677
  '[detection_thresholds] should be the same length as [filenames]'
1678
1678
 
1679
1679
  if rendering_thresholds is not None:
1680
- assert len(rendering_thresholds) == len(filenames)
1681
- '[rendering_thresholds] should be the same length as [filenames]'
1680
+ assert len(rendering_thresholds) == len(filenames), \
1681
+ '[rendering_thresholds] should be the same length as [filenames]'
1682
1682
  else:
1683
1683
  rendering_thresholds = [(x*0.6666) for x in detection_thresholds]
1684
1684
 
@@ -1932,32 +1932,54 @@ def find_equivalent_threshold(results_a,
1932
1932
 
1933
1933
  if False:
1934
1934
 
1935
+ #%% Prepare test files
1936
+
1937
+ from megadetector.utils.path_utils import insert_before_extension
1938
+
1939
+ model_names = ['mdv5a','mdv5b']
1940
+ image_folder = 'g:/temp/md-test-images'
1941
+ output_filename_base = os.path.join(image_folder,'comparison_test.json')
1942
+
1943
+ output_filenames = []
1944
+
1945
+ commands = []
1946
+
1947
+ for model_name in model_names:
1948
+ output_filename = insert_before_extension(output_filename_base,model_name)
1949
+ output_filenames.append(output_filename)
1950
+ cmd = 'python -m megadetector.detection.run_detector_batch'
1951
+ cmd += ' {} {} {} --recursive --output_relative_filenames'.format(
1952
+ model_name, image_folder,output_filename)
1953
+ commands.append(cmd)
1954
+
1955
+ cmd = '\n\n'.join(commands)
1956
+ print(cmd)
1957
+ import clipboard
1958
+ clipboard.copy(cmd)
1959
+
1960
+
1935
1961
  #%% Test two-way comparison
1936
1962
 
1937
1963
  options = BatchComparisonOptions()
1938
1964
 
1939
1965
  options.parallelize_rendering_with_threads = True
1940
1966
 
1941
- options.job_name = 'BCT'
1967
+ options.job_name = 'md-test-images'
1942
1968
  options.output_folder = r'g:\temp\comparisons'
1943
- options.image_folder = r'g:\camera_traps\camera_trap_images'
1969
+ options.image_folder = image_folder
1944
1970
  options.max_images_per_category = 100
1945
1971
  options.sort_by_confidence = True
1946
1972
 
1947
1973
  options.pairwise_options = []
1948
1974
 
1949
1975
  results_base = os.path.expanduser('~/postprocessing/bellevue-camera-traps')
1950
- filenames = [
1951
- os.path.join(results_base,r'bellevue-camera-traps-2023-12-05-v5a.0.0\combined_api_outputs\bellevue-camera-traps-2023-12-05-v5a.0.0_detections.json'),
1952
- os.path.join(results_base,r'bellevue-camera-traps-2023-12-05-aug-v5a.0.0\combined_api_outputs\bellevue-camera-traps-2023-12-05-aug-v5a.0.0_detections.json')
1953
- ]
1954
1976
 
1955
1977
  detection_thresholds = [0.15,0.15]
1956
1978
  rendering_thresholds = None
1957
1979
 
1958
- results = n_way_comparison(filenames,
1959
- options,
1960
- detection_thresholds,
1980
+ results = n_way_comparison(filenames=output_filenames,
1981
+ options=options,
1982
+ detection_thresholds=detection_thresholds,
1961
1983
  rendering_thresholds=rendering_thresholds)
1962
1984
 
1963
1985
  from megadetector.utils.path_utils import open_file
@@ -22,7 +22,8 @@ import pandas as pd
22
22
  from megadetector.postprocessing.load_api_results import load_api_results_csv
23
23
  from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
24
24
  from megadetector.data_management.annotations import annotation_constants
25
- from megadetector.utils import ct_utils
25
+ from megadetector.utils.ct_utils import get_max_conf
26
+ from megadetector.utils.ct_utils import write_json
26
27
 
27
28
  CONF_DIGITS = 3
28
29
 
@@ -138,7 +139,7 @@ def convert_json_to_csv(input_path,
138
139
  # print('Skipping failed image {} ({})'.format(im['file'],im['failure']))
139
140
  continue
140
141
 
141
- max_conf = ct_utils.get_max_conf(im)
142
+ max_conf = get_max_conf(im)
142
143
  detection_category_id_to_max_conf = defaultdict(float)
143
144
  classification_category_id_to_max_conf = defaultdict(float)
144
145
  detections = []
@@ -177,7 +178,8 @@ def convert_json_to_csv(input_path,
177
178
  classification_category_max = \
178
179
  classification_category_id_to_max_conf[classification_category_id]
179
180
  if classification_conf > classification_category_max:
180
- classification_category_id_to_max_conf[classification_category_id] = d['conf']
181
+ classification_category_id_to_max_conf[classification_category_id] = \
182
+ classification_conf
181
183
 
182
184
  # ...for each classification
183
185
 
@@ -210,7 +212,7 @@ def convert_json_to_csv(input_path,
210
212
 
211
213
  if omit_bounding_boxes:
212
214
  df = df.drop('detections',axis=1)
213
- df.to_csv(output_path,index=False,header=True)
215
+ df.to_csv(output_path,index=False,header=True,encoding=output_encoding)
214
216
 
215
217
  # ...def convert_json_to_csv(...)
216
218
 
@@ -295,7 +297,7 @@ def convert_csv_to_json(input_path,output_path=None,overwrite=True):
295
297
  json_out['classification_categories'] = classification_categories
296
298
  json_out['images'] = images
297
299
 
298
- json.dump(json_out,open(output_path,'w'),indent=1)
300
+ write_json(output_path,json_out)
299
301
 
300
302
  # ...def convert_csv_to_json(...)
301
303
 
@@ -372,7 +374,7 @@ def main():
372
374
  help='Output filename ending in .json or .csv (defaults to ' + \
373
375
  'input file, with .json/.csv replaced by .csv/.json)')
374
376
  parser.add_argument('--omit_bounding_boxes',action='store_true',
375
- help='Output bounding box text from .csv output (large and usually not useful)')
377
+ help='Omit bounding box text from .csv output (large and usually not useful)')
376
378
 
377
379
  if len(sys.argv[1:]) == 0:
378
380
  parser.print_help()
@@ -169,7 +169,9 @@ def crop_results_to_image_results(image_results_file_with_crop_ids,
169
169
  'Could not find image-level input file {}'.format(image_results_file_with_crop_ids)
170
170
  assert os.path.isfile(crop_results_file), \
171
171
  'Could not find crop results file {}'.format(crop_results_file)
172
- os.makedirs(os.path.dirname(output_file),exist_ok=True)
172
+ output_dir = os.path.dirname(output_file)
173
+ if len(output_dir) > 0:
174
+ os.makedirs(output_dir,exist_ok=True)
173
175
 
174
176
 
175
177
  ##%% Read input files
@@ -259,7 +261,11 @@ def crop_results_to_image_results(image_results_file_with_crop_ids,
259
261
  detections_without_classification_handling
260
262
  ))
261
263
 
262
- if not skip_detection:
264
+ if skip_detection:
265
+
266
+ n_skipped_detections += 1
267
+
268
+ else:
263
269
 
264
270
  crop_results_this_detection = crop_filename_to_results[crop_filename_relative]
265
271
 
@@ -340,8 +346,11 @@ def create_crop_folder(input_file,
340
346
  assert os.path.isfile(input_file), 'Input file {} not found'.format(input_file)
341
347
  assert os.path.isdir(input_folder), 'Input folder {} not found'.format(input_folder)
342
348
  os.makedirs(output_folder,exist_ok=True)
349
+
343
350
  if output_file is not None:
344
- os.makedirs(os.path.dirname(output_file),exist_ok=True)
351
+ output_dir = os.path.dirname(output_file)
352
+ if len(output_dir) > 0:
353
+ os.makedirs(output_dir,exist_ok=True)
345
354
 
346
355
 
347
356
  ##%% Read input
@@ -599,7 +608,7 @@ def main():
599
608
 
600
609
  print('Starting crop folder creation...')
601
610
  print('Input MD results: {}'.format(args.input_file))
602
- print('Input image folder {}'.format(args.input_folder))
611
+ print('Input image folder: {}'.format(args.input_folder))
603
612
  print('Output crop folder: {}'.format(args.output_folder))
604
613
 
605
614
  if args.output_file:
@@ -126,6 +126,7 @@ def generate_csv_report(md_results_file,
126
126
  recursive=True)
127
127
 
128
128
  else:
129
+
129
130
  assert os.path.isfile(datetime_source), \
130
131
  'datetime source {} is neither a folder nor a file'.format(datetime_source)
131
132
 
@@ -153,11 +154,14 @@ def generate_csv_report(md_results_file,
153
154
  print('Warning: a MD results file was supplied as the datetime source, but it does not appear '
154
155
  'to contain datetime information.')
155
156
 
157
+ # ...if datetime_source is a folder/file
158
+
156
159
  assert all_exif_results is not None
157
160
 
158
161
  filename_to_datetime_string = {}
159
162
 
160
163
  for exif_result in all_exif_results:
164
+
161
165
  datetime_string = unknown_datetime_tag
162
166
  if ('exif_tags' in exif_result) and \
163
167
  (exif_result['exif_tags'] is not None) and \
@@ -169,6 +173,8 @@ def generate_csv_report(md_results_file,
169
173
  assert isinstance(datetime_string,str), 'Unrecognized datetime format'
170
174
  filename_to_datetime_string[exif_result['file_name']] = datetime_string
171
175
 
176
+ # ...for each exif result
177
+
172
178
  image_files = [im['file'] for im in results['images']]
173
179
  image_files_set = set(image_files)
174
180
 
@@ -250,11 +256,10 @@ def generate_csv_report(md_results_file,
250
256
  base_record['filename'] = im['file'].replace('\\','/')
251
257
 
252
258
  # Datetime (if necessary)
259
+ datetime_string = ''
253
260
  if filename_to_datetime_string is not None:
254
261
  if im['file'] in filename_to_datetime_string:
255
262
  datetime_string = filename_to_datetime_string[im['file']]
256
- else:
257
- datetime_string = ''
258
263
  base_record['datetime'] = datetime_string
259
264
 
260
265
  for s in ['detection_category','max_detection_confidence',
@@ -383,13 +388,22 @@ def generate_csv_report(md_results_file,
383
388
  # ...for each image
384
389
 
385
390
  # Make sure every record has the same columns
386
- column_names = output_records[0].keys()
387
- for record in output_records:
388
- assert record.keys() == column_names
389
391
 
390
- # Write to .csv
391
- df = pd.DataFrame(output_records)
392
- df.to_csv(output_file,header=True,index=False)
392
+ if len(output_records) == 0:
393
+ print('Warning: no output records generated')
394
+ else:
395
+ column_names = output_records[0].keys()
396
+ for record in output_records:
397
+ assert record.keys() == column_names
398
+
399
+ # Create folder for output file if necessary
400
+ output_dir = os.path.dirname(output_file)
401
+ if len(output_dir) > 0:
402
+ os.makedirs(output_dir, exist_ok=True)
403
+
404
+ # Write to .csv
405
+ df = pd.DataFrame(output_records)
406
+ df.to_csv(output_file,header=True,index=False)
393
407
 
394
408
  # from megadetector.utils.path_utils import open_file; open_file(output_file)
395
409
 
@@ -23,7 +23,8 @@ from collections.abc import Mapping
23
23
 
24
24
  import pandas as pd
25
25
 
26
- from megadetector.utils import ct_utils
26
+ from megadetector.utils.ct_utils import get_max_conf
27
+ from megadetector.utils.ct_utils import write_json
27
28
  from megadetector.utils.wi_taxonomy_utils import load_md_or_speciesnet_file
28
29
 
29
30
 
@@ -85,7 +86,7 @@ def load_api_results(api_output_path: str, normalize_paths: bool = True,
85
86
  # add them, because our unofficial internal dataframe format includes this.
86
87
  for im in detection_results['images']:
87
88
  if 'max_detection_conf' not in im:
88
- im['max_detection_conf'] = ct_utils.get_max_conf(im)
89
+ im['max_detection_conf'] = get_max_conf(im)
89
90
 
90
91
  # Pack the json output into a Pandas DataFrame
91
92
  detection_results = pd.DataFrame(detection_results['images'])
@@ -139,8 +140,7 @@ def write_api_results(detection_results_table, other_fields, out_path):
139
140
  print('Warning: error removing max_detection_conf from output')
140
141
  pass
141
142
 
142
- with open(out_path, 'w') as f:
143
- json.dump(fields, f, indent=1)
143
+ write_json(out_path,fields)
144
144
 
145
145
  print('Finished writing detection results to {}'.format(out_path))
146
146
 
@@ -214,6 +214,10 @@ def write_api_results_csv(detection_results, filename):
214
214
 
215
215
  print('Writing detection results to {}'.format(filename))
216
216
 
217
+ output_dir = os.path.dirname(filename)
218
+ if len(output_dir) > 0:
219
+ os.makedirs(output_dir, exist_ok=True)
220
+
217
221
  detection_results.to_csv(filename, index=False)
218
222
 
219
223
  print('Finished writing detection results to {}'.format(filename))
@@ -22,6 +22,7 @@ from tqdm import tqdm
22
22
 
23
23
  from megadetector.visualization import visualization_utils as vis_utils
24
24
  from megadetector.utils.path_utils import insert_before_extension
25
+ from megadetector.utils.ct_utils import write_json
25
26
 
26
27
  default_confidence_threshold = 0.15
27
28
 
@@ -296,9 +297,7 @@ def md_to_coco(md_results_file,
296
297
  if verbose:
297
298
  print('Writing COCO output file...')
298
299
 
299
- if coco_output_file is not None:
300
- with open(coco_output_file,'w') as f:
301
- json.dump(output_dict,f,indent=1)
300
+ write_json(coco_output_file,output_dict)
302
301
 
303
302
  return output_dict
304
303
 
@@ -28,6 +28,7 @@ from functools import partial
28
28
 
29
29
  from megadetector.visualization.visualization_utils import open_image
30
30
  from megadetector.utils.ct_utils import round_float
31
+ from megadetector.utils.ct_utils import write_json
31
32
  from megadetector.detection.run_detector import DEFAULT_DETECTOR_LABEL_MAP, FAILURE_IMAGE_OPEN
32
33
 
33
34
  output_precision = 3
@@ -36,8 +37,11 @@ default_confidence_threshold = 0.15
36
37
 
37
38
  #%% Functions
38
39
 
39
- def get_labelme_dict_for_image(im,image_base_name=None,category_id_to_name=None,
40
- info=None,confidence_threshold=None):
40
+ def get_labelme_dict_for_image(im,
41
+ image_base_name=None,
42
+ category_id_to_name=None,
43
+ info=None,
44
+ confidence_threshold=None):
41
45
  """
42
46
  For the given image struct in MD results format, reformat the detections into
43
47
  labelme format.
@@ -60,7 +64,7 @@ def get_labelme_dict_for_image(im,image_base_name=None,category_id_to_name=None,
60
64
  if image_base_name is None:
61
65
  image_base_name = os.path.basename(im['file'])
62
66
 
63
- if category_id_to_name:
67
+ if category_id_to_name is None:
64
68
  category_id_to_name = DEFAULT_DETECTOR_LABEL_MAP
65
69
 
66
70
  if confidence_threshold is None:
@@ -138,8 +142,7 @@ def _write_output_for_image(im,
138
142
  info=info,
139
143
  confidence_threshold=confidence_threshold)
140
144
 
141
- with open(json_path,'w') as f:
142
- json.dump(output_dict,f,indent=1)
145
+ write_json(json_path,output_dict)
143
146
 
144
147
  # ...def write_output_for_image(...)
145
148
 
@@ -256,9 +259,10 @@ def md_to_labelme(results_file,
256
259
  md_results['images']),
257
260
  total=len(md_results['images'])))
258
261
  finally:
259
- pool.close()
260
- pool.join()
261
- print("Pool closed and joined for labelme file writes")
262
+ if pool is not None:
263
+ pool.close()
264
+ pool.join()
265
+ print("Pool closed and joined for labelme file writes")
262
266
 
263
267
  # ...for each image
264
268
 
@@ -10,6 +10,7 @@ Converts the MD .json format to the WI predictions.json format.
10
10
 
11
11
  import sys
12
12
  import argparse
13
+
13
14
  from megadetector.utils.wi_taxonomy_utils import generate_predictions_json_from_md_results
14
15
 
15
16
 
@@ -34,7 +35,7 @@ def main(): # noqa
34
35
 
35
36
  generate_predictions_json_from_md_results(args.md_results_file,
36
37
  args.predictions_json_file,
37
- base_folder=None)
38
+ base_folder=args.base_folder)
38
39
 
39
40
  if __name__ == '__main__':
40
41
  main()
@@ -23,6 +23,7 @@ import os
23
23
  from tqdm import tqdm
24
24
 
25
25
  from megadetector.utils.ct_utils import get_iou
26
+ from megadetector.utils.ct_utils import write_json
26
27
 
27
28
 
28
29
  #%% Structs
@@ -121,8 +122,6 @@ def merge_detections(source_files,target_file,output_file,options=None):
121
122
 
122
123
  assert os.path.isfile(target_file)
123
124
 
124
- os.makedirs(os.path.dirname(output_file),exist_ok=True)
125
-
126
125
  with open(target_file,'r') as f:
127
126
  output_data = json.load(f)
128
127
 
@@ -290,8 +289,7 @@ def merge_detections(source_files,target_file,output_file,options=None):
290
289
 
291
290
  # ...for each source file
292
291
 
293
- with open(output_file,'w') as f:
294
- json.dump(output_data,f,indent=1)
292
+ write_json(output_file,output_data)
295
293
 
296
294
  print('Saved merged results to {}'.format(output_file))
297
295
 
@@ -308,7 +306,7 @@ def main():
308
306
  default_options = MergeDetectionsOptions()
309
307
 
310
308
  parser = argparse.ArgumentParser(
311
- description='Merge detections from one or more MegaDetector results files into an existing reuslts file')
309
+ description='Merge detections from one or more MegaDetector results files into an existing results file')
312
310
  parser.add_argument(
313
311
  'source_files',
314
312
  nargs='+',
@@ -359,7 +357,7 @@ def main():
359
357
  type=int,
360
358
  nargs='+',
361
359
  default=None,
362
- help='List of numeric detection categories to include')
360
+ help='List of numeric detection categories to exclude')
363
361
  parser.add_argument(
364
362
  '--merge_empty_only',
365
363
  action='store_true',
@@ -1889,8 +1889,9 @@ def process_batch_results(options):
1889
1889
  if options.include_classification_category_report:
1890
1890
 
1891
1891
  # TODO: it's only for silly historical reasons that we re-read
1892
- # the input file in this case; we're not currently carrying the json
1893
- # representation around, only the Pandas representation.
1892
+ # the input file in this case; because this module has used Pandas
1893
+ # forever, we're not currently carrying the json representation around,
1894
+ # only the Pandas representation.
1894
1895
 
1895
1896
  print('Generating classification category report')
1896
1897
 
@@ -1905,7 +1906,7 @@ def process_batch_results(options):
1905
1906
  if ('classifications' in det) and (len(det['classifications']) > 0):
1906
1907
  class_id = det['classifications'][0][0]
1907
1908
  if class_id not in classification_category_to_count:
1908
- classification_category_to_count[class_id] = 0
1909
+ classification_category_to_count[class_id] = 1
1909
1910
  else:
1910
1911
  classification_category_to_count[class_id] = \
1911
1912
  classification_category_to_count[class_id] + 1
@@ -18,6 +18,7 @@ import argparse
18
18
  from tqdm import tqdm
19
19
 
20
20
  from megadetector.utils.ct_utils import invert_dictionary
21
+ from megadetector.utils.ct_utils import write_json
21
22
 
22
23
 
23
24
  #%% Main function
@@ -132,14 +133,16 @@ def remap_detection_categories(input_file,
132
133
  for det in im['detections']:
133
134
  det['category'] = input_category_id_to_output_category_id[det['category']]
134
135
 
135
- input_data['detection_categories'] = target_category_map
136
+ # ...for each image
136
137
 
137
- with open(output_file,'w') as f:
138
- json.dump(input_data,f,indent=1)
138
+ input_data['detection_categories'] = target_category_map
139
139
 
140
+ write_json(output_file,input_data)
140
141
 
141
142
  print('Saved remapped results to {}'.format(output_file))
142
143
 
144
+ # ...def remap_detection_categories(...)
145
+
143
146
 
144
147
  #%% Interactive driver
145
148