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.

Files changed (132) hide show
  1. api/batch_processing/data_preparation/manage_local_batch.py +302 -263
  2. api/batch_processing/data_preparation/manage_video_batch.py +81 -2
  3. api/batch_processing/postprocessing/add_max_conf.py +1 -0
  4. api/batch_processing/postprocessing/categorize_detections_by_size.py +50 -19
  5. api/batch_processing/postprocessing/compare_batch_results.py +110 -60
  6. api/batch_processing/postprocessing/load_api_results.py +56 -70
  7. api/batch_processing/postprocessing/md_to_coco.py +1 -1
  8. api/batch_processing/postprocessing/md_to_labelme.py +2 -1
  9. api/batch_processing/postprocessing/postprocess_batch_results.py +240 -81
  10. api/batch_processing/postprocessing/render_detection_confusion_matrix.py +625 -0
  11. api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +71 -23
  12. api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +1 -1
  13. api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +227 -75
  14. api/batch_processing/postprocessing/subset_json_detector_output.py +132 -5
  15. api/batch_processing/postprocessing/top_folders_to_bottom.py +1 -1
  16. api/synchronous/api_core/animal_detection_api/detection/run_detector_batch.py +2 -2
  17. classification/prepare_classification_script.py +191 -191
  18. data_management/coco_to_yolo.py +68 -45
  19. data_management/databases/integrity_check_json_db.py +7 -5
  20. data_management/generate_crops_from_cct.py +3 -3
  21. data_management/get_image_sizes.py +8 -6
  22. data_management/importers/add_timestamps_to_icct.py +79 -0
  23. data_management/importers/animl_results_to_md_results.py +160 -0
  24. data_management/importers/auckland_doc_test_to_json.py +4 -4
  25. data_management/importers/auckland_doc_to_json.py +1 -1
  26. data_management/importers/awc_to_json.py +5 -5
  27. data_management/importers/bellevue_to_json.py +5 -5
  28. data_management/importers/carrizo_shrubfree_2018.py +5 -5
  29. data_management/importers/carrizo_trail_cam_2017.py +5 -5
  30. data_management/importers/cct_field_adjustments.py +2 -3
  31. data_management/importers/channel_islands_to_cct.py +4 -4
  32. data_management/importers/ena24_to_json.py +5 -5
  33. data_management/importers/helena_to_cct.py +10 -10
  34. data_management/importers/idaho-camera-traps.py +12 -12
  35. data_management/importers/idfg_iwildcam_lila_prep.py +8 -8
  36. data_management/importers/jb_csv_to_json.py +4 -4
  37. data_management/importers/missouri_to_json.py +1 -1
  38. data_management/importers/noaa_seals_2019.py +1 -1
  39. data_management/importers/pc_to_json.py +5 -5
  40. data_management/importers/prepare-noaa-fish-data-for-lila.py +4 -4
  41. data_management/importers/prepare_zsl_imerit.py +5 -5
  42. data_management/importers/rspb_to_json.py +4 -4
  43. data_management/importers/save_the_elephants_survey_A.py +5 -5
  44. data_management/importers/save_the_elephants_survey_B.py +6 -6
  45. data_management/importers/snapshot_safari_importer.py +9 -9
  46. data_management/importers/snapshot_serengeti_lila.py +9 -9
  47. data_management/importers/timelapse_csv_set_to_json.py +5 -7
  48. data_management/importers/ubc_to_json.py +4 -4
  49. data_management/importers/umn_to_json.py +4 -4
  50. data_management/importers/wellington_to_json.py +1 -1
  51. data_management/importers/wi_to_json.py +2 -2
  52. data_management/importers/zamba_results_to_md_results.py +181 -0
  53. data_management/labelme_to_coco.py +35 -7
  54. data_management/labelme_to_yolo.py +229 -0
  55. data_management/lila/add_locations_to_island_camera_traps.py +1 -1
  56. data_management/lila/add_locations_to_nacti.py +147 -0
  57. data_management/lila/create_lila_blank_set.py +474 -0
  58. data_management/lila/create_lila_test_set.py +2 -1
  59. data_management/lila/create_links_to_md_results_files.py +106 -0
  60. data_management/lila/download_lila_subset.py +46 -21
  61. data_management/lila/generate_lila_per_image_labels.py +23 -14
  62. data_management/lila/get_lila_annotation_counts.py +17 -11
  63. data_management/lila/lila_common.py +14 -11
  64. data_management/lila/test_lila_metadata_urls.py +116 -0
  65. data_management/ocr_tools.py +829 -0
  66. data_management/resize_coco_dataset.py +13 -11
  67. data_management/yolo_output_to_md_output.py +84 -12
  68. data_management/yolo_to_coco.py +38 -20
  69. detection/process_video.py +36 -14
  70. detection/pytorch_detector.py +23 -8
  71. detection/run_detector.py +76 -19
  72. detection/run_detector_batch.py +178 -63
  73. detection/run_inference_with_yolov5_val.py +326 -57
  74. detection/run_tiled_inference.py +153 -43
  75. detection/video_utils.py +34 -8
  76. md_utils/ct_utils.py +172 -1
  77. md_utils/md_tests.py +372 -51
  78. md_utils/path_utils.py +167 -39
  79. md_utils/process_utils.py +26 -7
  80. md_utils/split_locations_into_train_val.py +215 -0
  81. md_utils/string_utils.py +10 -0
  82. md_utils/url_utils.py +0 -2
  83. md_utils/write_html_image_list.py +9 -26
  84. md_visualization/plot_utils.py +12 -8
  85. md_visualization/visualization_utils.py +106 -7
  86. md_visualization/visualize_db.py +16 -8
  87. md_visualization/visualize_detector_output.py +208 -97
  88. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/METADATA +3 -6
  89. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/RECORD +98 -121
  90. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/WHEEL +1 -1
  91. taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +1 -1
  92. taxonomy_mapping/map_new_lila_datasets.py +43 -39
  93. taxonomy_mapping/prepare_lila_taxonomy_release.py +5 -2
  94. taxonomy_mapping/preview_lila_taxonomy.py +27 -27
  95. taxonomy_mapping/species_lookup.py +33 -13
  96. taxonomy_mapping/taxonomy_csv_checker.py +7 -5
  97. api/synchronous/api_core/yolov5/detect.py +0 -252
  98. api/synchronous/api_core/yolov5/export.py +0 -607
  99. api/synchronous/api_core/yolov5/hubconf.py +0 -146
  100. api/synchronous/api_core/yolov5/models/__init__.py +0 -0
  101. api/synchronous/api_core/yolov5/models/common.py +0 -738
  102. api/synchronous/api_core/yolov5/models/experimental.py +0 -104
  103. api/synchronous/api_core/yolov5/models/tf.py +0 -574
  104. api/synchronous/api_core/yolov5/models/yolo.py +0 -338
  105. api/synchronous/api_core/yolov5/train.py +0 -670
  106. api/synchronous/api_core/yolov5/utils/__init__.py +0 -36
  107. api/synchronous/api_core/yolov5/utils/activations.py +0 -103
  108. api/synchronous/api_core/yolov5/utils/augmentations.py +0 -284
  109. api/synchronous/api_core/yolov5/utils/autoanchor.py +0 -170
  110. api/synchronous/api_core/yolov5/utils/autobatch.py +0 -66
  111. api/synchronous/api_core/yolov5/utils/aws/__init__.py +0 -0
  112. api/synchronous/api_core/yolov5/utils/aws/resume.py +0 -40
  113. api/synchronous/api_core/yolov5/utils/benchmarks.py +0 -148
  114. api/synchronous/api_core/yolov5/utils/callbacks.py +0 -71
  115. api/synchronous/api_core/yolov5/utils/dataloaders.py +0 -1087
  116. api/synchronous/api_core/yolov5/utils/downloads.py +0 -178
  117. api/synchronous/api_core/yolov5/utils/flask_rest_api/example_request.py +0 -19
  118. api/synchronous/api_core/yolov5/utils/flask_rest_api/restapi.py +0 -46
  119. api/synchronous/api_core/yolov5/utils/general.py +0 -1018
  120. api/synchronous/api_core/yolov5/utils/loggers/__init__.py +0 -187
  121. api/synchronous/api_core/yolov5/utils/loggers/wandb/__init__.py +0 -0
  122. api/synchronous/api_core/yolov5/utils/loggers/wandb/log_dataset.py +0 -27
  123. api/synchronous/api_core/yolov5/utils/loggers/wandb/sweep.py +0 -41
  124. api/synchronous/api_core/yolov5/utils/loggers/wandb/wandb_utils.py +0 -577
  125. api/synchronous/api_core/yolov5/utils/loss.py +0 -234
  126. api/synchronous/api_core/yolov5/utils/metrics.py +0 -355
  127. api/synchronous/api_core/yolov5/utils/plots.py +0 -489
  128. api/synchronous/api_core/yolov5/utils/torch_utils.py +0 -314
  129. api/synchronous/api_core/yolov5/val.py +0 -394
  130. md_utils/matlab_porting_tools.py +0 -97
  131. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/LICENSE +0 -0
  132. {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/top_level.txt +0 -0
@@ -1,104 +0,0 @@
1
- # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
2
- """
3
- Experimental modules
4
- """
5
- import math
6
-
7
- import numpy as np
8
- import torch
9
- import torch.nn as nn
10
-
11
- from models.common import Conv
12
- from utils.downloads import attempt_download
13
-
14
-
15
- class Sum(nn.Module):
16
- # Weighted sum of 2 or more layers https://arxiv.org/abs/1911.09070
17
- def __init__(self, n, weight=False): # n: number of inputs
18
- super().__init__()
19
- self.weight = weight # apply weights boolean
20
- self.iter = range(n - 1) # iter object
21
- if weight:
22
- self.w = nn.Parameter(-torch.arange(1.0, n) / 2, requires_grad=True) # layer weights
23
-
24
- def forward(self, x):
25
- y = x[0] # no weight
26
- if self.weight:
27
- w = torch.sigmoid(self.w) * 2
28
- for i in self.iter:
29
- y = y + x[i + 1] * w[i]
30
- else:
31
- for i in self.iter:
32
- y = y + x[i + 1]
33
- return y
34
-
35
-
36
- class MixConv2d(nn.Module):
37
- # Mixed Depth-wise Conv https://arxiv.org/abs/1907.09595
38
- def __init__(self, c1, c2, k=(1, 3), s=1, equal_ch=True): # ch_in, ch_out, kernel, stride, ch_strategy
39
- super().__init__()
40
- n = len(k) # number of convolutions
41
- if equal_ch: # equal c_ per group
42
- i = torch.linspace(0, n - 1E-6, c2).floor() # c2 indices
43
- c_ = [(i == g).sum() for g in range(n)] # intermediate channels
44
- else: # equal weight.numel() per group
45
- b = [c2] + [0] * n
46
- a = np.eye(n + 1, n, k=-1)
47
- a -= np.roll(a, 1, axis=1)
48
- a *= np.array(k) ** 2
49
- a[0] = 1
50
- c_ = np.linalg.lstsq(a, b, rcond=None)[0].round() # solve for equal weight indices, ax = b
51
-
52
- self.m = nn.ModuleList([
53
- nn.Conv2d(c1, int(c_), k, s, k // 2, groups=math.gcd(c1, int(c_)), bias=False) for k, c_ in zip(k, c_)])
54
- self.bn = nn.BatchNorm2d(c2)
55
- self.act = nn.SiLU()
56
-
57
- def forward(self, x):
58
- return self.act(self.bn(torch.cat([m(x) for m in self.m], 1)))
59
-
60
-
61
- class Ensemble(nn.ModuleList):
62
- # Ensemble of models
63
- def __init__(self):
64
- super().__init__()
65
-
66
- def forward(self, x, augment=False, profile=False, visualize=False):
67
- y = [module(x, augment, profile, visualize)[0] for module in self]
68
- # y = torch.stack(y).max(0)[0] # max ensemble
69
- # y = torch.stack(y).mean(0) # mean ensemble
70
- y = torch.cat(y, 1) # nms ensemble
71
- return y, None # inference, train output
72
-
73
-
74
- def attempt_load(weights, device=None, inplace=True, fuse=True):
75
- # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
76
- from models.yolo import Detect, Model
77
-
78
- model = Ensemble()
79
- for w in weights if isinstance(weights, list) else [weights]:
80
- ckpt = torch.load(attempt_download(w), map_location='cpu') # load
81
- ckpt = (ckpt.get('ema') or ckpt['model']).to(device).float() # FP32 model
82
- model.append(ckpt.fuse().eval() if fuse else ckpt.eval()) # fused or un-fused model in eval mode
83
-
84
- # Compatibility updates
85
- for m in model.modules():
86
- t = type(m)
87
- if t in (nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6, nn.SiLU, Detect, Model):
88
- m.inplace = inplace # torch 1.7.0 compatibility
89
- if t is Detect and not isinstance(m.anchor_grid, list):
90
- delattr(m, 'anchor_grid')
91
- setattr(m, 'anchor_grid', [torch.zeros(1)] * m.nl)
92
- elif t is Conv:
93
- m._non_persistent_buffers_set = set() # torch 1.6.0 compatibility
94
- elif t is nn.Upsample and not hasattr(m, 'recompute_scale_factor'):
95
- m.recompute_scale_factor = None # torch 1.11.0 compatibility
96
-
97
- if len(model) == 1:
98
- return model[-1] # return model
99
- print(f'Ensemble created with {weights}\n')
100
- for k in 'names', 'nc', 'yaml':
101
- setattr(model, k, getattr(model[0], k))
102
- model.stride = model[torch.argmax(torch.tensor([m.stride.max() for m in model])).int()].stride # max stride
103
- assert all(model[0].nc == m.nc for m in model), f'Models have different class counts: {[m.nc for m in model]}'
104
- return model # return ensemble
@@ -1,574 +0,0 @@
1
- # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
2
- """
3
- TensorFlow, Keras and TFLite versions of YOLOv5
4
- Authored by https://github.com/zldrobit in PR https://github.com/ultralytics/yolov5/pull/1127
5
-
6
- Usage:
7
- $ python models/tf.py --weights yolov5s.pt
8
-
9
- Export:
10
- $ python path/to/export.py --weights yolov5s.pt --include saved_model pb tflite tfjs
11
- """
12
-
13
- import argparse
14
- import sys
15
- from copy import deepcopy
16
- from pathlib import Path
17
-
18
- FILE = Path(__file__).resolve()
19
- ROOT = FILE.parents[1] # YOLOv5 root directory
20
- if str(ROOT) not in sys.path:
21
- sys.path.append(str(ROOT)) # add ROOT to PATH
22
- # ROOT = ROOT.relative_to(Path.cwd()) # relative
23
-
24
- import numpy as np
25
- import tensorflow as tf
26
- import torch
27
- import torch.nn as nn
28
- from tensorflow import keras
29
-
30
- from models.common import (C3, SPP, SPPF, Bottleneck, BottleneckCSP, C3x, Concat, Conv, CrossConv, DWConv,
31
- DWConvTranspose2d, Focus, autopad)
32
- from models.experimental import MixConv2d, attempt_load
33
- from models.yolo import Detect
34
- from utils.activations import SiLU
35
- from utils.general import LOGGER, make_divisible, print_args
36
-
37
-
38
- class TFBN(keras.layers.Layer):
39
- # TensorFlow BatchNormalization wrapper
40
- def __init__(self, w=None):
41
- super().__init__()
42
- self.bn = keras.layers.BatchNormalization(
43
- beta_initializer=keras.initializers.Constant(w.bias.numpy()),
44
- gamma_initializer=keras.initializers.Constant(w.weight.numpy()),
45
- moving_mean_initializer=keras.initializers.Constant(w.running_mean.numpy()),
46
- moving_variance_initializer=keras.initializers.Constant(w.running_var.numpy()),
47
- epsilon=w.eps)
48
-
49
- def call(self, inputs):
50
- return self.bn(inputs)
51
-
52
-
53
- class TFPad(keras.layers.Layer):
54
- # Pad inputs in spatial dimensions 1 and 2
55
- def __init__(self, pad):
56
- super().__init__()
57
- if isinstance(pad, int):
58
- self.pad = tf.constant([[0, 0], [pad, pad], [pad, pad], [0, 0]])
59
- else: # tuple/list
60
- self.pad = tf.constant([[0, 0], [pad[0], pad[0]], [pad[1], pad[1]], [0, 0]])
61
-
62
- def call(self, inputs):
63
- return tf.pad(inputs, self.pad, mode='constant', constant_values=0)
64
-
65
-
66
- class TFConv(keras.layers.Layer):
67
- # Standard convolution
68
- def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
69
- # ch_in, ch_out, weights, kernel, stride, padding, groups
70
- super().__init__()
71
- assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
72
- # TensorFlow convolution padding is inconsistent with PyTorch (e.g. k=3 s=2 'SAME' padding)
73
- # see https://stackoverflow.com/questions/52975843/comparing-conv2d-with-padding-between-tensorflow-and-pytorch
74
- conv = keras.layers.Conv2D(
75
- filters=c2,
76
- kernel_size=k,
77
- strides=s,
78
- padding='SAME' if s == 1 else 'VALID',
79
- use_bias=not hasattr(w, 'bn'),
80
- kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
81
- bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
82
- self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
83
- self.bn = TFBN(w.bn) if hasattr(w, 'bn') else tf.identity
84
- self.act = activations(w.act) if act else tf.identity
85
-
86
- def call(self, inputs):
87
- return self.act(self.bn(self.conv(inputs)))
88
-
89
-
90
- class TFDWConv(keras.layers.Layer):
91
- # Depthwise convolution
92
- def __init__(self, c1, c2, k=1, s=1, p=None, act=True, w=None):
93
- # ch_in, ch_out, weights, kernel, stride, padding, groups
94
- super().__init__()
95
- assert c2 % c1 == 0, f'TFDWConv() output={c2} must be a multiple of input={c1} channels'
96
- conv = keras.layers.DepthwiseConv2D(
97
- kernel_size=k,
98
- depth_multiplier=c2 // c1,
99
- strides=s,
100
- padding='SAME' if s == 1 else 'VALID',
101
- use_bias=not hasattr(w, 'bn'),
102
- depthwise_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
103
- bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
104
- self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
105
- self.bn = TFBN(w.bn) if hasattr(w, 'bn') else tf.identity
106
- self.act = activations(w.act) if act else tf.identity
107
-
108
- def call(self, inputs):
109
- return self.act(self.bn(self.conv(inputs)))
110
-
111
-
112
- class TFDWConvTranspose2d(keras.layers.Layer):
113
- # Depthwise ConvTranspose2d
114
- def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0, w=None):
115
- # ch_in, ch_out, weights, kernel, stride, padding, groups
116
- super().__init__()
117
- assert c1 == c2, f'TFDWConv() output={c2} must be equal to input={c1} channels'
118
- assert k == 4 and p1 == 1, 'TFDWConv() only valid for k=4 and p1=1'
119
- weight, bias = w.weight.permute(2, 3, 1, 0).numpy(), w.bias.numpy()
120
- self.c1 = c1
121
- self.conv = [
122
- keras.layers.Conv2DTranspose(filters=1,
123
- kernel_size=k,
124
- strides=s,
125
- padding='VALID',
126
- output_padding=p2,
127
- use_bias=True,
128
- kernel_initializer=keras.initializers.Constant(weight[..., i:i + 1]),
129
- bias_initializer=keras.initializers.Constant(bias[i])) for i in range(c1)]
130
-
131
- def call(self, inputs):
132
- return tf.concat([m(x) for m, x in zip(self.conv, tf.split(inputs, self.c1, 3))], 3)[:, 1:-1, 1:-1]
133
-
134
-
135
- class TFFocus(keras.layers.Layer):
136
- # Focus wh information into c-space
137
- def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
138
- # ch_in, ch_out, kernel, stride, padding, groups
139
- super().__init__()
140
- self.conv = TFConv(c1 * 4, c2, k, s, p, g, act, w.conv)
141
-
142
- def call(self, inputs): # x(b,w,h,c) -> y(b,w/2,h/2,4c)
143
- # inputs = inputs / 255 # normalize 0-255 to 0-1
144
- inputs = [inputs[:, ::2, ::2, :], inputs[:, 1::2, ::2, :], inputs[:, ::2, 1::2, :], inputs[:, 1::2, 1::2, :]]
145
- return self.conv(tf.concat(inputs, 3))
146
-
147
-
148
- class TFBottleneck(keras.layers.Layer):
149
- # Standard bottleneck
150
- def __init__(self, c1, c2, shortcut=True, g=1, e=0.5, w=None): # ch_in, ch_out, shortcut, groups, expansion
151
- super().__init__()
152
- c_ = int(c2 * e) # hidden channels
153
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
154
- self.cv2 = TFConv(c_, c2, 3, 1, g=g, w=w.cv2)
155
- self.add = shortcut and c1 == c2
156
-
157
- def call(self, inputs):
158
- return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
159
-
160
-
161
- class TFCrossConv(keras.layers.Layer):
162
- # Cross Convolution
163
- def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False, w=None):
164
- super().__init__()
165
- c_ = int(c2 * e) # hidden channels
166
- self.cv1 = TFConv(c1, c_, (1, k), (1, s), w=w.cv1)
167
- self.cv2 = TFConv(c_, c2, (k, 1), (s, 1), g=g, w=w.cv2)
168
- self.add = shortcut and c1 == c2
169
-
170
- def call(self, inputs):
171
- return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
172
-
173
-
174
- class TFConv2d(keras.layers.Layer):
175
- # Substitution for PyTorch nn.Conv2D
176
- def __init__(self, c1, c2, k, s=1, g=1, bias=True, w=None):
177
- super().__init__()
178
- assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
179
- self.conv = keras.layers.Conv2D(filters=c2,
180
- kernel_size=k,
181
- strides=s,
182
- padding='VALID',
183
- use_bias=bias,
184
- kernel_initializer=keras.initializers.Constant(
185
- w.weight.permute(2, 3, 1, 0).numpy()),
186
- bias_initializer=keras.initializers.Constant(w.bias.numpy()) if bias else None)
187
-
188
- def call(self, inputs):
189
- return self.conv(inputs)
190
-
191
-
192
- class TFBottleneckCSP(keras.layers.Layer):
193
- # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
194
- def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
195
- # ch_in, ch_out, number, shortcut, groups, expansion
196
- super().__init__()
197
- c_ = int(c2 * e) # hidden channels
198
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
199
- self.cv2 = TFConv2d(c1, c_, 1, 1, bias=False, w=w.cv2)
200
- self.cv3 = TFConv2d(c_, c_, 1, 1, bias=False, w=w.cv3)
201
- self.cv4 = TFConv(2 * c_, c2, 1, 1, w=w.cv4)
202
- self.bn = TFBN(w.bn)
203
- self.act = lambda x: keras.activations.swish(x)
204
- self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
205
-
206
- def call(self, inputs):
207
- y1 = self.cv3(self.m(self.cv1(inputs)))
208
- y2 = self.cv2(inputs)
209
- return self.cv4(self.act(self.bn(tf.concat((y1, y2), axis=3))))
210
-
211
-
212
- class TFC3(keras.layers.Layer):
213
- # CSP Bottleneck with 3 convolutions
214
- def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
215
- # ch_in, ch_out, number, shortcut, groups, expansion
216
- super().__init__()
217
- c_ = int(c2 * e) # hidden channels
218
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
219
- self.cv2 = TFConv(c1, c_, 1, 1, w=w.cv2)
220
- self.cv3 = TFConv(2 * c_, c2, 1, 1, w=w.cv3)
221
- self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
222
-
223
- def call(self, inputs):
224
- return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
225
-
226
-
227
- class TFC3x(keras.layers.Layer):
228
- # 3 module with cross-convolutions
229
- def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
230
- # ch_in, ch_out, number, shortcut, groups, expansion
231
- super().__init__()
232
- c_ = int(c2 * e) # hidden channels
233
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
234
- self.cv2 = TFConv(c1, c_, 1, 1, w=w.cv2)
235
- self.cv3 = TFConv(2 * c_, c2, 1, 1, w=w.cv3)
236
- self.m = keras.Sequential([
237
- TFCrossConv(c_, c_, k=3, s=1, g=g, e=1.0, shortcut=shortcut, w=w.m[j]) for j in range(n)])
238
-
239
- def call(self, inputs):
240
- return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
241
-
242
-
243
- class TFSPP(keras.layers.Layer):
244
- # Spatial pyramid pooling layer used in YOLOv3-SPP
245
- def __init__(self, c1, c2, k=(5, 9, 13), w=None):
246
- super().__init__()
247
- c_ = c1 // 2 # hidden channels
248
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
249
- self.cv2 = TFConv(c_ * (len(k) + 1), c2, 1, 1, w=w.cv2)
250
- self.m = [keras.layers.MaxPool2D(pool_size=x, strides=1, padding='SAME') for x in k]
251
-
252
- def call(self, inputs):
253
- x = self.cv1(inputs)
254
- return self.cv2(tf.concat([x] + [m(x) for m in self.m], 3))
255
-
256
-
257
- class TFSPPF(keras.layers.Layer):
258
- # Spatial pyramid pooling-Fast layer
259
- def __init__(self, c1, c2, k=5, w=None):
260
- super().__init__()
261
- c_ = c1 // 2 # hidden channels
262
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
263
- self.cv2 = TFConv(c_ * 4, c2, 1, 1, w=w.cv2)
264
- self.m = keras.layers.MaxPool2D(pool_size=k, strides=1, padding='SAME')
265
-
266
- def call(self, inputs):
267
- x = self.cv1(inputs)
268
- y1 = self.m(x)
269
- y2 = self.m(y1)
270
- return self.cv2(tf.concat([x, y1, y2, self.m(y2)], 3))
271
-
272
-
273
- class TFDetect(keras.layers.Layer):
274
- # TF YOLOv5 Detect layer
275
- def __init__(self, nc=80, anchors=(), ch=(), imgsz=(640, 640), w=None): # detection layer
276
- super().__init__()
277
- self.stride = tf.convert_to_tensor(w.stride.numpy(), dtype=tf.float32)
278
- self.nc = nc # number of classes
279
- self.no = nc + 5 # number of outputs per anchor
280
- self.nl = len(anchors) # number of detection layers
281
- self.na = len(anchors[0]) // 2 # number of anchors
282
- self.grid = [tf.zeros(1)] * self.nl # init grid
283
- self.anchors = tf.convert_to_tensor(w.anchors.numpy(), dtype=tf.float32)
284
- self.anchor_grid = tf.reshape(self.anchors * tf.reshape(self.stride, [self.nl, 1, 1]), [self.nl, 1, -1, 1, 2])
285
- self.m = [TFConv2d(x, self.no * self.na, 1, w=w.m[i]) for i, x in enumerate(ch)]
286
- self.training = False # set to False after building model
287
- self.imgsz = imgsz
288
- for i in range(self.nl):
289
- ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
290
- self.grid[i] = self._make_grid(nx, ny)
291
-
292
- def call(self, inputs):
293
- z = [] # inference output
294
- x = []
295
- for i in range(self.nl):
296
- x.append(self.m[i](inputs[i]))
297
- # x(bs,20,20,255) to x(bs,3,20,20,85)
298
- ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
299
- x[i] = tf.reshape(x[i], [-1, ny * nx, self.na, self.no])
300
-
301
- if not self.training: # inference
302
- y = tf.sigmoid(x[i])
303
- grid = tf.transpose(self.grid[i], [0, 2, 1, 3]) - 0.5
304
- anchor_grid = tf.transpose(self.anchor_grid[i], [0, 2, 1, 3]) * 4
305
- xy = (y[..., 0:2] * 2 + grid) * self.stride[i] # xy
306
- wh = y[..., 2:4] ** 2 * anchor_grid
307
- # Normalize xywh to 0-1 to reduce calibration error
308
- xy /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
309
- wh /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
310
- y = tf.concat([xy, wh, y[..., 4:]], -1)
311
- z.append(tf.reshape(y, [-1, self.na * ny * nx, self.no]))
312
-
313
- return tf.transpose(x, [0, 2, 1, 3]) if self.training else (tf.concat(z, 1), x)
314
-
315
- @staticmethod
316
- def _make_grid(nx=20, ny=20):
317
- # yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
318
- # return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
319
- xv, yv = tf.meshgrid(tf.range(nx), tf.range(ny))
320
- return tf.cast(tf.reshape(tf.stack([xv, yv], 2), [1, 1, ny * nx, 2]), dtype=tf.float32)
321
-
322
-
323
- class TFUpsample(keras.layers.Layer):
324
- # TF version of torch.nn.Upsample()
325
- def __init__(self, size, scale_factor, mode, w=None): # warning: all arguments needed including 'w'
326
- super().__init__()
327
- assert scale_factor == 2, "scale_factor must be 2"
328
- self.upsample = lambda x: tf.image.resize(x, (x.shape[1] * 2, x.shape[2] * 2), method=mode)
329
- # self.upsample = keras.layers.UpSampling2D(size=scale_factor, interpolation=mode)
330
- # with default arguments: align_corners=False, half_pixel_centers=False
331
- # self.upsample = lambda x: tf.raw_ops.ResizeNearestNeighbor(images=x,
332
- # size=(x.shape[1] * 2, x.shape[2] * 2))
333
-
334
- def call(self, inputs):
335
- return self.upsample(inputs)
336
-
337
-
338
- class TFConcat(keras.layers.Layer):
339
- # TF version of torch.concat()
340
- def __init__(self, dimension=1, w=None):
341
- super().__init__()
342
- assert dimension == 1, "convert only NCHW to NHWC concat"
343
- self.d = 3
344
-
345
- def call(self, inputs):
346
- return tf.concat(inputs, self.d)
347
-
348
-
349
- def parse_model(d, ch, model, imgsz): # model_dict, input_channels(3)
350
- LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}")
351
- anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
352
- na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
353
- no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
354
-
355
- layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
356
- for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args
357
- m_str = m
358
- m = eval(m) if isinstance(m, str) else m # eval strings
359
- for j, a in enumerate(args):
360
- try:
361
- args[j] = eval(a) if isinstance(a, str) else a # eval strings
362
- except NameError:
363
- pass
364
-
365
- n = max(round(n * gd), 1) if n > 1 else n # depth gain
366
- if m in [
367
- nn.Conv2d, Conv, DWConv, DWConvTranspose2d, Bottleneck, SPP, SPPF, MixConv2d, Focus, CrossConv,
368
- BottleneckCSP, C3, C3x]:
369
- c1, c2 = ch[f], args[0]
370
- c2 = make_divisible(c2 * gw, 8) if c2 != no else c2
371
-
372
- args = [c1, c2, *args[1:]]
373
- if m in [BottleneckCSP, C3, C3x]:
374
- args.insert(2, n)
375
- n = 1
376
- elif m is nn.BatchNorm2d:
377
- args = [ch[f]]
378
- elif m is Concat:
379
- c2 = sum(ch[-1 if x == -1 else x + 1] for x in f)
380
- elif m is Detect:
381
- args.append([ch[x + 1] for x in f])
382
- if isinstance(args[1], int): # number of anchors
383
- args[1] = [list(range(args[1] * 2))] * len(f)
384
- args.append(imgsz)
385
- else:
386
- c2 = ch[f]
387
-
388
- tf_m = eval('TF' + m_str.replace('nn.', ''))
389
- m_ = keras.Sequential([tf_m(*args, w=model.model[i][j]) for j in range(n)]) if n > 1 \
390
- else tf_m(*args, w=model.model[i]) # module
391
-
392
- torch_m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
393
- t = str(m)[8:-2].replace('__main__.', '') # module type
394
- np = sum(x.numel() for x in torch_m_.parameters()) # number params
395
- m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params
396
- LOGGER.info(f'{i:>3}{str(f):>18}{str(n):>3}{np:>10} {t:<40}{str(args):<30}') # print
397
- save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
398
- layers.append(m_)
399
- ch.append(c2)
400
- return keras.Sequential(layers), sorted(save)
401
-
402
-
403
- class TFModel:
404
- # TF YOLOv5 model
405
- def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, model=None, imgsz=(640, 640)): # model, channels, classes
406
- super().__init__()
407
- if isinstance(cfg, dict):
408
- self.yaml = cfg # model dict
409
- else: # is *.yaml
410
- import yaml # for torch hub
411
- self.yaml_file = Path(cfg).name
412
- with open(cfg) as f:
413
- self.yaml = yaml.load(f, Loader=yaml.FullLoader) # model dict
414
-
415
- # Define model
416
- if nc and nc != self.yaml['nc']:
417
- LOGGER.info(f"Overriding {cfg} nc={self.yaml['nc']} with nc={nc}")
418
- self.yaml['nc'] = nc # override yaml value
419
- self.model, self.savelist = parse_model(deepcopy(self.yaml), ch=[ch], model=model, imgsz=imgsz)
420
-
421
- def predict(self,
422
- inputs,
423
- tf_nms=False,
424
- agnostic_nms=False,
425
- topk_per_class=100,
426
- topk_all=100,
427
- iou_thres=0.45,
428
- conf_thres=0.25):
429
- y = [] # outputs
430
- x = inputs
431
- for m in self.model.layers:
432
- if m.f != -1: # if not from previous layer
433
- x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers
434
-
435
- x = m(x) # run
436
- y.append(x if m.i in self.savelist else None) # save output
437
-
438
- # Add TensorFlow NMS
439
- if tf_nms:
440
- boxes = self._xywh2xyxy(x[0][..., :4])
441
- probs = x[0][:, :, 4:5]
442
- classes = x[0][:, :, 5:]
443
- scores = probs * classes
444
- if agnostic_nms:
445
- nms = AgnosticNMS()((boxes, classes, scores), topk_all, iou_thres, conf_thres)
446
- else:
447
- boxes = tf.expand_dims(boxes, 2)
448
- nms = tf.image.combined_non_max_suppression(boxes,
449
- scores,
450
- topk_per_class,
451
- topk_all,
452
- iou_thres,
453
- conf_thres,
454
- clip_boxes=False)
455
- return nms, x[1]
456
- return x[0] # output only first tensor [1,6300,85] = [xywh, conf, class0, class1, ...]
457
- # x = x[0][0] # [x(1,6300,85), ...] to x(6300,85)
458
- # xywh = x[..., :4] # x(6300,4) boxes
459
- # conf = x[..., 4:5] # x(6300,1) confidences
460
- # cls = tf.reshape(tf.cast(tf.argmax(x[..., 5:], axis=1), tf.float32), (-1, 1)) # x(6300,1) classes
461
- # return tf.concat([conf, cls, xywh], 1)
462
-
463
- @staticmethod
464
- def _xywh2xyxy(xywh):
465
- # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
466
- x, y, w, h = tf.split(xywh, num_or_size_splits=4, axis=-1)
467
- return tf.concat([x - w / 2, y - h / 2, x + w / 2, y + h / 2], axis=-1)
468
-
469
-
470
- class AgnosticNMS(keras.layers.Layer):
471
- # TF Agnostic NMS
472
- def call(self, input, topk_all, iou_thres, conf_thres):
473
- # wrap map_fn to avoid TypeSpec related error https://stackoverflow.com/a/65809989/3036450
474
- return tf.map_fn(lambda x: self._nms(x, topk_all, iou_thres, conf_thres),
475
- input,
476
- fn_output_signature=(tf.float32, tf.float32, tf.float32, tf.int32),
477
- name='agnostic_nms')
478
-
479
- @staticmethod
480
- def _nms(x, topk_all=100, iou_thres=0.45, conf_thres=0.25): # agnostic NMS
481
- boxes, classes, scores = x
482
- class_inds = tf.cast(tf.argmax(classes, axis=-1), tf.float32)
483
- scores_inp = tf.reduce_max(scores, -1)
484
- selected_inds = tf.image.non_max_suppression(boxes,
485
- scores_inp,
486
- max_output_size=topk_all,
487
- iou_threshold=iou_thres,
488
- score_threshold=conf_thres)
489
- selected_boxes = tf.gather(boxes, selected_inds)
490
- padded_boxes = tf.pad(selected_boxes,
491
- paddings=[[0, topk_all - tf.shape(selected_boxes)[0]], [0, 0]],
492
- mode="CONSTANT",
493
- constant_values=0.0)
494
- selected_scores = tf.gather(scores_inp, selected_inds)
495
- padded_scores = tf.pad(selected_scores,
496
- paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
497
- mode="CONSTANT",
498
- constant_values=-1.0)
499
- selected_classes = tf.gather(class_inds, selected_inds)
500
- padded_classes = tf.pad(selected_classes,
501
- paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
502
- mode="CONSTANT",
503
- constant_values=-1.0)
504
- valid_detections = tf.shape(selected_inds)[0]
505
- return padded_boxes, padded_scores, padded_classes, valid_detections
506
-
507
-
508
- def activations(act=nn.SiLU):
509
- # Returns TF activation from input PyTorch activation
510
- if isinstance(act, nn.LeakyReLU):
511
- return lambda x: keras.activations.relu(x, alpha=0.1)
512
- elif isinstance(act, nn.Hardswish):
513
- return lambda x: x * tf.nn.relu6(x + 3) * 0.166666667
514
- elif isinstance(act, (nn.SiLU, SiLU)):
515
- return lambda x: keras.activations.swish(x)
516
- else:
517
- raise Exception(f'no matching TensorFlow activation found for PyTorch activation {act}')
518
-
519
-
520
- def representative_dataset_gen(dataset, ncalib=100):
521
- # Representative dataset generator for use with converter.representative_dataset, returns a generator of np arrays
522
- for n, (path, img, im0s, vid_cap, string) in enumerate(dataset):
523
- im = np.transpose(img, [1, 2, 0])
524
- im = np.expand_dims(im, axis=0).astype(np.float32)
525
- im /= 255
526
- yield [im]
527
- if n >= ncalib:
528
- break
529
-
530
-
531
- def run(
532
- weights=ROOT / 'yolov5s.pt', # weights path
533
- imgsz=(640, 640), # inference size h,w
534
- batch_size=1, # batch size
535
- dynamic=False, # dynamic batch size
536
- ):
537
- # PyTorch model
538
- im = torch.zeros((batch_size, 3, *imgsz)) # BCHW image
539
- model = attempt_load(weights, device=torch.device('cpu'), inplace=True, fuse=False)
540
- _ = model(im) # inference
541
- model.info()
542
-
543
- # TensorFlow model
544
- im = tf.zeros((batch_size, *imgsz, 3)) # BHWC image
545
- tf_model = TFModel(cfg=model.yaml, model=model, nc=model.nc, imgsz=imgsz)
546
- _ = tf_model.predict(im) # inference
547
-
548
- # Keras model
549
- im = keras.Input(shape=(*imgsz, 3), batch_size=None if dynamic else batch_size)
550
- keras_model = keras.Model(inputs=im, outputs=tf_model.predict(im))
551
- keras_model.summary()
552
-
553
- LOGGER.info('PyTorch, TensorFlow and Keras models successfully verified.\nUse export.py for TF model export.')
554
-
555
-
556
- def parse_opt():
557
- parser = argparse.ArgumentParser()
558
- parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s.pt', help='weights path')
559
- parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
560
- parser.add_argument('--batch-size', type=int, default=1, help='batch size')
561
- parser.add_argument('--dynamic', action='store_true', help='dynamic batch size')
562
- opt = parser.parse_args()
563
- opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
564
- print_args(vars(opt))
565
- return opt
566
-
567
-
568
- def main(opt):
569
- run(**vars(opt))
570
-
571
-
572
- if __name__ == "__main__":
573
- opt = parse_opt()
574
- main(opt)