megadetector 5.0.5__py3-none-any.whl → 5.0.7__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.
- api/batch_processing/data_preparation/manage_local_batch.py +302 -263
- api/batch_processing/data_preparation/manage_video_batch.py +81 -2
- api/batch_processing/postprocessing/add_max_conf.py +1 -0
- api/batch_processing/postprocessing/categorize_detections_by_size.py +50 -19
- api/batch_processing/postprocessing/compare_batch_results.py +110 -60
- api/batch_processing/postprocessing/load_api_results.py +56 -70
- api/batch_processing/postprocessing/md_to_coco.py +1 -1
- api/batch_processing/postprocessing/md_to_labelme.py +2 -1
- api/batch_processing/postprocessing/postprocess_batch_results.py +240 -81
- api/batch_processing/postprocessing/render_detection_confusion_matrix.py +625 -0
- api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +71 -23
- api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +1 -1
- api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +227 -75
- api/batch_processing/postprocessing/subset_json_detector_output.py +132 -5
- api/batch_processing/postprocessing/top_folders_to_bottom.py +1 -1
- api/synchronous/api_core/animal_detection_api/detection/run_detector_batch.py +2 -2
- classification/prepare_classification_script.py +191 -191
- data_management/coco_to_yolo.py +68 -45
- data_management/databases/integrity_check_json_db.py +7 -5
- data_management/generate_crops_from_cct.py +3 -3
- data_management/get_image_sizes.py +8 -6
- data_management/importers/add_timestamps_to_icct.py +79 -0
- data_management/importers/animl_results_to_md_results.py +160 -0
- data_management/importers/auckland_doc_test_to_json.py +4 -4
- data_management/importers/auckland_doc_to_json.py +1 -1
- data_management/importers/awc_to_json.py +5 -5
- data_management/importers/bellevue_to_json.py +5 -5
- data_management/importers/carrizo_shrubfree_2018.py +5 -5
- data_management/importers/carrizo_trail_cam_2017.py +5 -5
- data_management/importers/cct_field_adjustments.py +2 -3
- data_management/importers/channel_islands_to_cct.py +4 -4
- data_management/importers/ena24_to_json.py +5 -5
- data_management/importers/helena_to_cct.py +10 -10
- data_management/importers/idaho-camera-traps.py +12 -12
- data_management/importers/idfg_iwildcam_lila_prep.py +8 -8
- data_management/importers/jb_csv_to_json.py +4 -4
- data_management/importers/missouri_to_json.py +1 -1
- data_management/importers/noaa_seals_2019.py +1 -1
- data_management/importers/pc_to_json.py +5 -5
- data_management/importers/prepare-noaa-fish-data-for-lila.py +4 -4
- data_management/importers/prepare_zsl_imerit.py +5 -5
- data_management/importers/rspb_to_json.py +4 -4
- data_management/importers/save_the_elephants_survey_A.py +5 -5
- data_management/importers/save_the_elephants_survey_B.py +6 -6
- data_management/importers/snapshot_safari_importer.py +9 -9
- data_management/importers/snapshot_serengeti_lila.py +9 -9
- data_management/importers/timelapse_csv_set_to_json.py +5 -7
- data_management/importers/ubc_to_json.py +4 -4
- data_management/importers/umn_to_json.py +4 -4
- data_management/importers/wellington_to_json.py +1 -1
- data_management/importers/wi_to_json.py +2 -2
- data_management/importers/zamba_results_to_md_results.py +181 -0
- data_management/labelme_to_coco.py +35 -7
- data_management/labelme_to_yolo.py +229 -0
- data_management/lila/add_locations_to_island_camera_traps.py +1 -1
- data_management/lila/add_locations_to_nacti.py +147 -0
- data_management/lila/create_lila_blank_set.py +474 -0
- data_management/lila/create_lila_test_set.py +2 -1
- data_management/lila/create_links_to_md_results_files.py +106 -0
- data_management/lila/download_lila_subset.py +46 -21
- data_management/lila/generate_lila_per_image_labels.py +23 -14
- data_management/lila/get_lila_annotation_counts.py +17 -11
- data_management/lila/lila_common.py +14 -11
- data_management/lila/test_lila_metadata_urls.py +116 -0
- data_management/ocr_tools.py +829 -0
- data_management/resize_coco_dataset.py +13 -11
- data_management/yolo_output_to_md_output.py +84 -12
- data_management/yolo_to_coco.py +38 -20
- detection/process_video.py +36 -14
- detection/pytorch_detector.py +23 -8
- detection/run_detector.py +76 -19
- detection/run_detector_batch.py +178 -63
- detection/run_inference_with_yolov5_val.py +326 -57
- detection/run_tiled_inference.py +153 -43
- detection/video_utils.py +34 -8
- md_utils/ct_utils.py +172 -1
- md_utils/md_tests.py +372 -51
- md_utils/path_utils.py +167 -39
- md_utils/process_utils.py +26 -7
- md_utils/split_locations_into_train_val.py +215 -0
- md_utils/string_utils.py +10 -0
- md_utils/url_utils.py +0 -2
- md_utils/write_html_image_list.py +9 -26
- md_visualization/plot_utils.py +12 -8
- md_visualization/visualization_utils.py +106 -7
- md_visualization/visualize_db.py +16 -8
- md_visualization/visualize_detector_output.py +208 -97
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/METADATA +3 -6
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/RECORD +98 -121
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/WHEEL +1 -1
- taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +1 -1
- taxonomy_mapping/map_new_lila_datasets.py +43 -39
- taxonomy_mapping/prepare_lila_taxonomy_release.py +5 -2
- taxonomy_mapping/preview_lila_taxonomy.py +27 -27
- taxonomy_mapping/species_lookup.py +33 -13
- taxonomy_mapping/taxonomy_csv_checker.py +7 -5
- api/synchronous/api_core/yolov5/detect.py +0 -252
- api/synchronous/api_core/yolov5/export.py +0 -607
- api/synchronous/api_core/yolov5/hubconf.py +0 -146
- api/synchronous/api_core/yolov5/models/__init__.py +0 -0
- api/synchronous/api_core/yolov5/models/common.py +0 -738
- api/synchronous/api_core/yolov5/models/experimental.py +0 -104
- api/synchronous/api_core/yolov5/models/tf.py +0 -574
- api/synchronous/api_core/yolov5/models/yolo.py +0 -338
- api/synchronous/api_core/yolov5/train.py +0 -670
- api/synchronous/api_core/yolov5/utils/__init__.py +0 -36
- api/synchronous/api_core/yolov5/utils/activations.py +0 -103
- api/synchronous/api_core/yolov5/utils/augmentations.py +0 -284
- api/synchronous/api_core/yolov5/utils/autoanchor.py +0 -170
- api/synchronous/api_core/yolov5/utils/autobatch.py +0 -66
- api/synchronous/api_core/yolov5/utils/aws/__init__.py +0 -0
- api/synchronous/api_core/yolov5/utils/aws/resume.py +0 -40
- api/synchronous/api_core/yolov5/utils/benchmarks.py +0 -148
- api/synchronous/api_core/yolov5/utils/callbacks.py +0 -71
- api/synchronous/api_core/yolov5/utils/dataloaders.py +0 -1087
- api/synchronous/api_core/yolov5/utils/downloads.py +0 -178
- api/synchronous/api_core/yolov5/utils/flask_rest_api/example_request.py +0 -19
- api/synchronous/api_core/yolov5/utils/flask_rest_api/restapi.py +0 -46
- api/synchronous/api_core/yolov5/utils/general.py +0 -1018
- api/synchronous/api_core/yolov5/utils/loggers/__init__.py +0 -187
- api/synchronous/api_core/yolov5/utils/loggers/wandb/__init__.py +0 -0
- api/synchronous/api_core/yolov5/utils/loggers/wandb/log_dataset.py +0 -27
- api/synchronous/api_core/yolov5/utils/loggers/wandb/sweep.py +0 -41
- api/synchronous/api_core/yolov5/utils/loggers/wandb/wandb_utils.py +0 -577
- api/synchronous/api_core/yolov5/utils/loss.py +0 -234
- api/synchronous/api_core/yolov5/utils/metrics.py +0 -355
- api/synchronous/api_core/yolov5/utils/plots.py +0 -489
- api/synchronous/api_core/yolov5/utils/torch_utils.py +0 -314
- api/synchronous/api_core/yolov5/val.py +0 -394
- md_utils/matlab_porting_tools.py +0 -97
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/LICENSE +0 -0
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/top_level.txt +0 -0
|
@@ -8,16 +8,13 @@
|
|
|
8
8
|
# Each "filename" can also be a dict with elements 'filename','title',
|
|
9
9
|
# 'imageStyle','textStyle', 'linkTarget'
|
|
10
10
|
#
|
|
11
|
-
# Strips directory information away if options.makeRelative == 1.
|
|
12
|
-
#
|
|
13
|
-
# Tries to convert absolute to relative paths if options.makeRelative == 2.
|
|
14
|
-
#
|
|
15
11
|
########
|
|
16
12
|
|
|
17
13
|
#%% Constants and imports
|
|
18
14
|
|
|
19
15
|
import os
|
|
20
16
|
import math
|
|
17
|
+
import urllib
|
|
21
18
|
|
|
22
19
|
from md_utils import path_utils
|
|
23
20
|
|
|
@@ -39,12 +36,12 @@ def write_html_image_list(filename=None,images=None,options=None):
|
|
|
39
36
|
options: a dict with one or more of the following fields:
|
|
40
37
|
|
|
41
38
|
fHtml
|
|
42
|
-
makeRelative
|
|
43
39
|
headerHtml
|
|
44
40
|
trailerHtml
|
|
45
41
|
defaultTextStyle
|
|
46
42
|
defaultImageStyle
|
|
47
43
|
maxFiguresPerHtmlFile
|
|
44
|
+
urlEncodeFilenames (default True, e.g. '#' will be replaced by '%23')
|
|
48
45
|
|
|
49
46
|
"""
|
|
50
47
|
|
|
@@ -55,9 +52,6 @@ def write_html_image_list(filename=None,images=None,options=None):
|
|
|
55
52
|
if 'fHtml' not in options:
|
|
56
53
|
options['fHtml'] = -1
|
|
57
54
|
|
|
58
|
-
if 'makeRelative' not in options:
|
|
59
|
-
options['makeRelative'] = 0
|
|
60
|
-
|
|
61
55
|
if 'headerHtml' not in options or options['headerHtml'] is None:
|
|
62
56
|
options['headerHtml'] = ''
|
|
63
57
|
|
|
@@ -71,6 +65,9 @@ def write_html_image_list(filename=None,images=None,options=None):
|
|
|
71
65
|
if 'defaultImageStyle' not in options or options['defaultImageStyle'] is None:
|
|
72
66
|
options['defaultImageStyle'] = \
|
|
73
67
|
"margin:0px;margin-top:5px;margin-bottom:5px;"
|
|
68
|
+
|
|
69
|
+
if 'urlEncodeFilenames' not in options or options['urlEncodeFilenames'] is None:
|
|
70
|
+
options['urlEncodeFilenames'] = True
|
|
74
71
|
|
|
75
72
|
# Possibly split the html output for figures into multiple files; Chrome gets sad with
|
|
76
73
|
# thousands of images in a single tab.
|
|
@@ -98,24 +95,6 @@ def write_html_image_list(filename=None,images=None,options=None):
|
|
|
98
95
|
imageInfo['textStyle'] = options['defaultTextStyle']
|
|
99
96
|
images[iImage] = imageInfo
|
|
100
97
|
|
|
101
|
-
# Remove leading directory information from filenames if requested
|
|
102
|
-
if options['makeRelative'] == 1:
|
|
103
|
-
|
|
104
|
-
for iImage in range(0,len(images)):
|
|
105
|
-
_,n,e = path_utils.fileparts(images[iImage]['filename'])
|
|
106
|
-
images[iImage]['filename'] = n + e
|
|
107
|
-
|
|
108
|
-
elif options['makeRelative'] == 2:
|
|
109
|
-
|
|
110
|
-
baseDir,_,_ = path_utils.fileparts(filename)
|
|
111
|
-
if len(baseDir) > 1 and baseDir[-1] != '\\':
|
|
112
|
-
baseDir = baseDir + '\\'
|
|
113
|
-
|
|
114
|
-
for iImage in range(0,len(images)):
|
|
115
|
-
fn = images[iImage]['filename']
|
|
116
|
-
fn = fn.replace(baseDir,'')
|
|
117
|
-
images[iImage]['filename'] = fn
|
|
118
|
-
|
|
119
98
|
nImages = len(images)
|
|
120
99
|
|
|
121
100
|
# If we need to break this up into multiple files...
|
|
@@ -197,6 +176,10 @@ def write_html_image_list(filename=None,images=None,options=None):
|
|
|
197
176
|
title = title.encode('ascii','ignore').decode('ascii')
|
|
198
177
|
filename = filename.encode('ascii','ignore').decode('ascii')
|
|
199
178
|
|
|
179
|
+
if options['urlEncodeFilenames']:
|
|
180
|
+
filename = filename.replace('\\','/')
|
|
181
|
+
filename = urllib.parse.quote(filename)
|
|
182
|
+
|
|
200
183
|
if len(title) > 0:
|
|
201
184
|
fHtml.write(
|
|
202
185
|
'<p style="{}">{}</p>\n'\
|
md_visualization/plot_utils.py
CHANGED
|
@@ -28,8 +28,9 @@ def plot_confusion_matrix(
|
|
|
28
28
|
cmap: Union[str, matplotlib.colors.Colormap] = matplotlib.cm.Blues,
|
|
29
29
|
vmax: Optional[float] = None,
|
|
30
30
|
use_colorbar: bool = True,
|
|
31
|
-
y_label: bool = True,
|
|
32
|
-
fmt: str = '{:.0f}'
|
|
31
|
+
y_label: bool = True,
|
|
32
|
+
fmt: str = '{:.0f}',
|
|
33
|
+
fig=None
|
|
33
34
|
) -> matplotlib.figure.Figure:
|
|
34
35
|
"""
|
|
35
36
|
Plot a confusion matrix. By default, assumes values in the given matrix
|
|
@@ -56,18 +57,20 @@ def plot_confusion_matrix(
|
|
|
56
57
|
assert matrix.shape[1] == num_classes
|
|
57
58
|
assert len(classes) == num_classes
|
|
58
59
|
|
|
60
|
+
normalized_matrix = matrix.astype(np.float64) / (
|
|
61
|
+
matrix.sum(axis=1, keepdims=True) + 1e-7)
|
|
59
62
|
if normalize:
|
|
60
|
-
matrix =
|
|
61
|
-
matrix.sum(axis=1, keepdims=True) + 1e-7)
|
|
63
|
+
matrix = normalized_matrix
|
|
62
64
|
|
|
63
65
|
fig_h = 3 + 0.3 * num_classes
|
|
64
66
|
fig_w = fig_h
|
|
65
67
|
if use_colorbar:
|
|
66
68
|
fig_w += 0.5
|
|
67
69
|
|
|
68
|
-
fig
|
|
70
|
+
if fig is None:
|
|
71
|
+
fig = matplotlib.figure.Figure(figsize=(fig_w, fig_h), tight_layout=True)
|
|
69
72
|
ax = fig.subplots(1, 1)
|
|
70
|
-
im = ax.imshow(
|
|
73
|
+
im = ax.imshow(normalized_matrix, interpolation='nearest', cmap=cmap, vmax=vmax)
|
|
71
74
|
ax.set_title(title)
|
|
72
75
|
|
|
73
76
|
if use_colorbar:
|
|
@@ -86,10 +89,11 @@ def plot_confusion_matrix(
|
|
|
86
89
|
ax.set_ylabel('Ground-truth class')
|
|
87
90
|
|
|
88
91
|
for i, j in np.ndindex(matrix.shape):
|
|
89
|
-
|
|
92
|
+
v = matrix[i, j]
|
|
93
|
+
ax.text(j, i, fmt.format(v),
|
|
90
94
|
horizontalalignment='center',
|
|
91
95
|
verticalalignment='center',
|
|
92
|
-
color='white' if
|
|
96
|
+
color='white' if normalized_matrix[i, j] > 0.5 else 'black')
|
|
93
97
|
|
|
94
98
|
return fig
|
|
95
99
|
|
|
@@ -166,13 +166,26 @@ def load_image(input_file: Union[str, BytesIO]) -> Image:
|
|
|
166
166
|
return image
|
|
167
167
|
|
|
168
168
|
|
|
169
|
-
def resize_image(image, target_width, target_height=-1):
|
|
169
|
+
def resize_image(image, target_width, target_height=-1, output_file=None):
|
|
170
170
|
"""
|
|
171
171
|
Resizes a PIL image object to the specified width and height; does not resize
|
|
172
172
|
in place. If either width or height are -1, resizes with aspect ratio preservation.
|
|
173
173
|
If both are -1, returns the original image (does not copy in this case).
|
|
174
|
+
|
|
175
|
+
None is equivalent to -1 for target_width and target_height.
|
|
176
|
+
|
|
177
|
+
[image] can be a PIL image or a filename.
|
|
174
178
|
"""
|
|
175
179
|
|
|
180
|
+
if isinstance(image,str):
|
|
181
|
+
image = load_image(image)
|
|
182
|
+
|
|
183
|
+
if target_width is None:
|
|
184
|
+
target_width = -1
|
|
185
|
+
|
|
186
|
+
if target_height is None:
|
|
187
|
+
target_height = -1
|
|
188
|
+
|
|
176
189
|
# Null operation
|
|
177
190
|
if target_width == -1 and target_height == -1:
|
|
178
191
|
return image
|
|
@@ -197,6 +210,9 @@ def resize_image(image, target_width, target_height=-1):
|
|
|
197
210
|
except:
|
|
198
211
|
resized_image = image.resize((target_width, target_height), Image.Resampling.LANCZOS)
|
|
199
212
|
|
|
213
|
+
if output_file is not None:
|
|
214
|
+
exif_preserving_save(resized_image,output_file)
|
|
215
|
+
|
|
200
216
|
return resized_image
|
|
201
217
|
|
|
202
218
|
|
|
@@ -363,7 +379,8 @@ def render_detection_bounding_boxes(detections, image,
|
|
|
363
379
|
The type of the numerical label (default string) needs to be consistent with the keys in
|
|
364
380
|
label_map; no casting is carried out. If this is None, no classification labels are shown.
|
|
365
381
|
|
|
366
|
-
confidence_threshold: optional, threshold above which
|
|
382
|
+
confidence_threshold: optional, threshold above which boxes are rendered. Can also be a dictionary
|
|
383
|
+
mapping category IDs to thresholds.
|
|
367
384
|
|
|
368
385
|
thickness: line thickness in pixels. Default value is 4.
|
|
369
386
|
|
|
@@ -397,9 +414,15 @@ def render_detection_bounding_boxes(detections, image,
|
|
|
397
414
|
|
|
398
415
|
score = detection['conf']
|
|
399
416
|
|
|
417
|
+
if isinstance(confidence_threshold,dict):
|
|
418
|
+
rendering_threshold = confidence_threshold[detection['category']]
|
|
419
|
+
else:
|
|
420
|
+
rendering_threshold = confidence_threshold
|
|
421
|
+
|
|
422
|
+
|
|
400
423
|
# Always render objects with a confidence of "None", this is typically used
|
|
401
424
|
# for ground truth data.
|
|
402
|
-
if score is None or score >=
|
|
425
|
+
if score is None or score >= rendering_threshold:
|
|
403
426
|
|
|
404
427
|
x1, y1, w_box, h_box = detection['bbox']
|
|
405
428
|
display_boxes.append([y1, x1, y1 + h_box, x1 + w_box])
|
|
@@ -784,7 +807,8 @@ def draw_bounding_boxes_on_file(input_file, output_file, detections, confidence_
|
|
|
784
807
|
detector_label_map=DEFAULT_DETECTOR_LABEL_MAP,
|
|
785
808
|
thickness=DEFAULT_BOX_THICKNESS, expansion=0,
|
|
786
809
|
colormap=DEFAULT_COLORS,
|
|
787
|
-
|
|
810
|
+
label_font_size=DEFAULT_LABEL_FONT_SIZE,
|
|
811
|
+
custom_strings=None,target_size=None):
|
|
788
812
|
"""
|
|
789
813
|
Render detection bounding boxes on an image loaded from file, writing the results to a
|
|
790
814
|
new image file.
|
|
@@ -804,15 +828,21 @@ def draw_bounding_boxes_on_file(input_file, output_file, detections, confidence_
|
|
|
804
828
|
custom_strings: optional set of strings to append to detection labels, should have the
|
|
805
829
|
same length as [detections]. Appended before classification labels, if classification
|
|
806
830
|
data is provided.
|
|
831
|
+
|
|
832
|
+
target_size: tuple of (target_width,target_height). Either or both can be -1,
|
|
833
|
+
see resize_image for documentation. If None or (-1,-1), uses the original image size.
|
|
807
834
|
"""
|
|
808
835
|
|
|
809
836
|
image = open_image(input_file)
|
|
810
|
-
|
|
837
|
+
|
|
838
|
+
if target_size is not None:
|
|
839
|
+
image = resize_image(image,target_size[0],target_size[1])
|
|
840
|
+
|
|
811
841
|
render_detection_bounding_boxes(
|
|
812
842
|
detections, image, label_map=detector_label_map,
|
|
813
843
|
confidence_threshold=confidence_threshold,
|
|
814
844
|
thickness=thickness,expansion=expansion,colormap=colormap,
|
|
815
|
-
custom_strings=custom_strings)
|
|
845
|
+
custom_strings=custom_strings,label_font_size=label_font_size)
|
|
816
846
|
|
|
817
847
|
image.save(output_file)
|
|
818
848
|
|
|
@@ -838,4 +868,73 @@ def draw_db_boxes_on_file(input_file, output_file, boxes, classes=None,
|
|
|
838
868
|
|
|
839
869
|
image.save(output_file)
|
|
840
870
|
|
|
841
|
-
|
|
871
|
+
|
|
872
|
+
def gray_scale_fraction(image,crop_size=(0.1,0.1)):
|
|
873
|
+
"""
|
|
874
|
+
Returns the fraction of the pixels in [image] that appear to be grayscale (R==G==B),
|
|
875
|
+
useful for approximating whether this is a night-time image when flash information is not
|
|
876
|
+
available in EXIF data (or for video frames, where this information is often not available
|
|
877
|
+
in structured metadata at all).
|
|
878
|
+
|
|
879
|
+
[image] can be a PIL image or a file name.
|
|
880
|
+
|
|
881
|
+
crop_size should be a 2-element list/tuple, representing the fraction of the image
|
|
882
|
+
to crop at the top and bottom, respectively, before analyzing (to minimize the possibility
|
|
883
|
+
of including color elements in the image chrome).
|
|
884
|
+
"""
|
|
885
|
+
|
|
886
|
+
if isinstance(image,str):
|
|
887
|
+
image = Image.open(image)
|
|
888
|
+
|
|
889
|
+
if image.mode == 'L':
|
|
890
|
+
return 1.0
|
|
891
|
+
|
|
892
|
+
if len(image.getbands()) == 1:
|
|
893
|
+
return 1.0
|
|
894
|
+
|
|
895
|
+
# Crop if necessary
|
|
896
|
+
if crop_size[0] > 0 or crop_size[1] > 0:
|
|
897
|
+
|
|
898
|
+
assert (crop_size[0] + crop_size[1]) < 1.0, \
|
|
899
|
+
print('Illegal crop size: {}'.format(str(crop_size)))
|
|
900
|
+
|
|
901
|
+
top_crop_pixels = int(image.height * crop_size[0])
|
|
902
|
+
bottom_crop_pixels = int(image.height * crop_size[1])
|
|
903
|
+
|
|
904
|
+
left = 0
|
|
905
|
+
right = image.width
|
|
906
|
+
|
|
907
|
+
# Remove pixels from the top
|
|
908
|
+
first_crop_top = top_crop_pixels
|
|
909
|
+
first_crop_bottom = image.height
|
|
910
|
+
first_crop = image.crop((left, first_crop_top, right, first_crop_bottom))
|
|
911
|
+
|
|
912
|
+
# Remove pixels from the bottom
|
|
913
|
+
second_crop_top = 0
|
|
914
|
+
second_crop_bottom = first_crop.height - bottom_crop_pixels
|
|
915
|
+
second_crop = first_crop.crop((left, second_crop_top, right, second_crop_bottom))
|
|
916
|
+
|
|
917
|
+
image = second_crop
|
|
918
|
+
|
|
919
|
+
# It doesn't matter if these are actually R/G/B, they're just names
|
|
920
|
+
r = np.array(image.getchannel(0))
|
|
921
|
+
g = np.array(image.getchannel(1))
|
|
922
|
+
b = np.array(image.getchannel(2))
|
|
923
|
+
|
|
924
|
+
gray_pixels = np.logical_and(r == g, r == b)
|
|
925
|
+
n_pixels = gray_pixels.size
|
|
926
|
+
n_gray_pixels = gray_pixels.sum()
|
|
927
|
+
|
|
928
|
+
return n_gray_pixels / n_pixels
|
|
929
|
+
|
|
930
|
+
# Non-numpy way to do the same thing, briefly keeping this here for posterity
|
|
931
|
+
if False:
|
|
932
|
+
|
|
933
|
+
w, h = image.size
|
|
934
|
+
n_pixels = w*h
|
|
935
|
+
n_gray_pixels = 0
|
|
936
|
+
for i in range(w):
|
|
937
|
+
for j in range(h):
|
|
938
|
+
r, g, b = image.getpixel((i,j))
|
|
939
|
+
if r == g and r == b and g == b:
|
|
940
|
+
n_gray_pixels += 1
|
md_visualization/visualize_db.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# visualize_db.py
|
|
4
4
|
#
|
|
5
5
|
# Outputs an HTML page visualizing annotations (class labels and/or bounding boxes)
|
|
6
|
-
# on a sample of images in a database in the COCO Camera Traps format
|
|
6
|
+
# on a sample of images in a database in the COCO Camera Traps format.
|
|
7
7
|
#
|
|
8
8
|
########
|
|
9
9
|
|
|
@@ -41,7 +41,15 @@ class DbVizOptions:
|
|
|
41
41
|
#
|
|
42
42
|
# If viz_size is None or (-1,-1), the original image size is used.
|
|
43
43
|
viz_size = (675, -1)
|
|
44
|
+
|
|
45
|
+
# The most relevant option one might want to set here is:
|
|
46
|
+
#
|
|
47
|
+
# htmlOptions['maxFiguresPerHtmlFile']
|
|
48
|
+
#
|
|
49
|
+
# ...which can be used to paginate previews to a number of images that will load well
|
|
50
|
+
# in a browser (5000 is a reasonable limit).
|
|
44
51
|
htmlOptions = write_html_image_list()
|
|
52
|
+
|
|
45
53
|
sort_by_filename = True
|
|
46
54
|
trim_to_images_with_bboxes = False
|
|
47
55
|
|
|
@@ -96,7 +104,7 @@ def image_filename_to_path(image_file_name, image_base_dir, pathsep_replacement=
|
|
|
96
104
|
|
|
97
105
|
#%% Core functions
|
|
98
106
|
|
|
99
|
-
def
|
|
107
|
+
def visualize_db(db_path, output_dir, image_base_dir, options=None):
|
|
100
108
|
"""
|
|
101
109
|
Writes images and html to output_dir to visualize the annotations in the json file
|
|
102
110
|
db_path.
|
|
@@ -418,7 +426,7 @@ def process_images(db_path, output_dir, image_base_dir, options=None):
|
|
|
418
426
|
|
|
419
427
|
return htmlOutputFile,image_db
|
|
420
428
|
|
|
421
|
-
# def
|
|
429
|
+
# def visualize_db(...)
|
|
422
430
|
|
|
423
431
|
|
|
424
432
|
#%% Command-line driver
|
|
@@ -452,7 +460,8 @@ def main():
|
|
|
452
460
|
parser.add_argument('--random_seed', action='store', type=int, default=None,
|
|
453
461
|
help='Random seed for image selection')
|
|
454
462
|
parser.add_argument('--pathsep_replacement', action='store', type=str, default='',
|
|
455
|
-
help='Replace path separators in relative filenames with another
|
|
463
|
+
help='Replace path separators in relative filenames with another ' + \
|
|
464
|
+
'character (frequently ~)')
|
|
456
465
|
|
|
457
466
|
if len(sys.argv[1:]) == 0:
|
|
458
467
|
parser.print_help()
|
|
@@ -466,7 +475,7 @@ def main():
|
|
|
466
475
|
if options.random_sort:
|
|
467
476
|
options.sort_by_filename = False
|
|
468
477
|
|
|
469
|
-
|
|
478
|
+
visualize_db(options.db_path,options.output_dir,options.image_base_dir,options)
|
|
470
479
|
|
|
471
480
|
|
|
472
481
|
if __name__ == '__main__':
|
|
@@ -474,7 +483,7 @@ if __name__ == '__main__':
|
|
|
474
483
|
main()
|
|
475
484
|
|
|
476
485
|
|
|
477
|
-
#%% Interactive driver
|
|
486
|
+
#%% Interactive driver
|
|
478
487
|
|
|
479
488
|
if False:
|
|
480
489
|
|
|
@@ -487,6 +496,5 @@ if False:
|
|
|
487
496
|
options = DbVizOptions()
|
|
488
497
|
options.num_to_visualize = 100
|
|
489
498
|
|
|
490
|
-
htmlOutputFile,db =
|
|
499
|
+
htmlOutputFile,db = visualize_db(db_path,output_dir,image_base_dir,options)
|
|
491
500
|
# os.startfile(htmlOutputFile)
|
|
492
|
-
|