bplusplus 1.1.0__py3-none-any.whl → 1.2.1__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 bplusplus might be problematic. Click here for more details.

Files changed (97) hide show
  1. bplusplus/__init__.py +4 -2
  2. bplusplus/collect.py +72 -3
  3. bplusplus/hierarchical/test.py +670 -0
  4. bplusplus/hierarchical/train.py +676 -0
  5. bplusplus/prepare.py +236 -71
  6. bplusplus/resnet/test.py +473 -0
  7. bplusplus/resnet/train.py +329 -0
  8. bplusplus-1.2.1.dist-info/METADATA +252 -0
  9. bplusplus-1.2.1.dist-info/RECORD +12 -0
  10. bplusplus/yolov5detect/__init__.py +0 -1
  11. bplusplus/yolov5detect/detect.py +0 -444
  12. bplusplus/yolov5detect/export.py +0 -1530
  13. bplusplus/yolov5detect/insect.yaml +0 -8
  14. bplusplus/yolov5detect/models/__init__.py +0 -0
  15. bplusplus/yolov5detect/models/common.py +0 -1109
  16. bplusplus/yolov5detect/models/experimental.py +0 -130
  17. bplusplus/yolov5detect/models/hub/anchors.yaml +0 -56
  18. bplusplus/yolov5detect/models/hub/yolov3-spp.yaml +0 -52
  19. bplusplus/yolov5detect/models/hub/yolov3-tiny.yaml +0 -42
  20. bplusplus/yolov5detect/models/hub/yolov3.yaml +0 -52
  21. bplusplus/yolov5detect/models/hub/yolov5-bifpn.yaml +0 -49
  22. bplusplus/yolov5detect/models/hub/yolov5-fpn.yaml +0 -43
  23. bplusplus/yolov5detect/models/hub/yolov5-p2.yaml +0 -55
  24. bplusplus/yolov5detect/models/hub/yolov5-p34.yaml +0 -42
  25. bplusplus/yolov5detect/models/hub/yolov5-p6.yaml +0 -57
  26. bplusplus/yolov5detect/models/hub/yolov5-p7.yaml +0 -68
  27. bplusplus/yolov5detect/models/hub/yolov5-panet.yaml +0 -49
  28. bplusplus/yolov5detect/models/hub/yolov5l6.yaml +0 -61
  29. bplusplus/yolov5detect/models/hub/yolov5m6.yaml +0 -61
  30. bplusplus/yolov5detect/models/hub/yolov5n6.yaml +0 -61
  31. bplusplus/yolov5detect/models/hub/yolov5s-LeakyReLU.yaml +0 -50
  32. bplusplus/yolov5detect/models/hub/yolov5s-ghost.yaml +0 -49
  33. bplusplus/yolov5detect/models/hub/yolov5s-transformer.yaml +0 -49
  34. bplusplus/yolov5detect/models/hub/yolov5s6.yaml +0 -61
  35. bplusplus/yolov5detect/models/hub/yolov5x6.yaml +0 -61
  36. bplusplus/yolov5detect/models/segment/yolov5l-seg.yaml +0 -49
  37. bplusplus/yolov5detect/models/segment/yolov5m-seg.yaml +0 -49
  38. bplusplus/yolov5detect/models/segment/yolov5n-seg.yaml +0 -49
  39. bplusplus/yolov5detect/models/segment/yolov5s-seg.yaml +0 -49
  40. bplusplus/yolov5detect/models/segment/yolov5x-seg.yaml +0 -49
  41. bplusplus/yolov5detect/models/tf.py +0 -797
  42. bplusplus/yolov5detect/models/yolo.py +0 -495
  43. bplusplus/yolov5detect/models/yolov5l.yaml +0 -49
  44. bplusplus/yolov5detect/models/yolov5m.yaml +0 -49
  45. bplusplus/yolov5detect/models/yolov5n.yaml +0 -49
  46. bplusplus/yolov5detect/models/yolov5s.yaml +0 -49
  47. bplusplus/yolov5detect/models/yolov5x.yaml +0 -49
  48. bplusplus/yolov5detect/utils/__init__.py +0 -97
  49. bplusplus/yolov5detect/utils/activations.py +0 -134
  50. bplusplus/yolov5detect/utils/augmentations.py +0 -448
  51. bplusplus/yolov5detect/utils/autoanchor.py +0 -175
  52. bplusplus/yolov5detect/utils/autobatch.py +0 -70
  53. bplusplus/yolov5detect/utils/aws/__init__.py +0 -0
  54. bplusplus/yolov5detect/utils/aws/mime.sh +0 -26
  55. bplusplus/yolov5detect/utils/aws/resume.py +0 -41
  56. bplusplus/yolov5detect/utils/aws/userdata.sh +0 -27
  57. bplusplus/yolov5detect/utils/callbacks.py +0 -72
  58. bplusplus/yolov5detect/utils/dataloaders.py +0 -1385
  59. bplusplus/yolov5detect/utils/docker/Dockerfile +0 -73
  60. bplusplus/yolov5detect/utils/docker/Dockerfile-arm64 +0 -40
  61. bplusplus/yolov5detect/utils/docker/Dockerfile-cpu +0 -42
  62. bplusplus/yolov5detect/utils/downloads.py +0 -136
  63. bplusplus/yolov5detect/utils/flask_rest_api/README.md +0 -70
  64. bplusplus/yolov5detect/utils/flask_rest_api/example_request.py +0 -17
  65. bplusplus/yolov5detect/utils/flask_rest_api/restapi.py +0 -49
  66. bplusplus/yolov5detect/utils/general.py +0 -1294
  67. bplusplus/yolov5detect/utils/google_app_engine/Dockerfile +0 -25
  68. bplusplus/yolov5detect/utils/google_app_engine/additional_requirements.txt +0 -6
  69. bplusplus/yolov5detect/utils/google_app_engine/app.yaml +0 -16
  70. bplusplus/yolov5detect/utils/loggers/__init__.py +0 -476
  71. bplusplus/yolov5detect/utils/loggers/clearml/README.md +0 -222
  72. bplusplus/yolov5detect/utils/loggers/clearml/__init__.py +0 -0
  73. bplusplus/yolov5detect/utils/loggers/clearml/clearml_utils.py +0 -230
  74. bplusplus/yolov5detect/utils/loggers/clearml/hpo.py +0 -90
  75. bplusplus/yolov5detect/utils/loggers/comet/README.md +0 -250
  76. bplusplus/yolov5detect/utils/loggers/comet/__init__.py +0 -551
  77. bplusplus/yolov5detect/utils/loggers/comet/comet_utils.py +0 -151
  78. bplusplus/yolov5detect/utils/loggers/comet/hpo.py +0 -126
  79. bplusplus/yolov5detect/utils/loggers/comet/optimizer_config.json +0 -135
  80. bplusplus/yolov5detect/utils/loggers/wandb/__init__.py +0 -0
  81. bplusplus/yolov5detect/utils/loggers/wandb/wandb_utils.py +0 -210
  82. bplusplus/yolov5detect/utils/loss.py +0 -259
  83. bplusplus/yolov5detect/utils/metrics.py +0 -381
  84. bplusplus/yolov5detect/utils/plots.py +0 -517
  85. bplusplus/yolov5detect/utils/segment/__init__.py +0 -0
  86. bplusplus/yolov5detect/utils/segment/augmentations.py +0 -100
  87. bplusplus/yolov5detect/utils/segment/dataloaders.py +0 -366
  88. bplusplus/yolov5detect/utils/segment/general.py +0 -160
  89. bplusplus/yolov5detect/utils/segment/loss.py +0 -198
  90. bplusplus/yolov5detect/utils/segment/metrics.py +0 -225
  91. bplusplus/yolov5detect/utils/segment/plots.py +0 -152
  92. bplusplus/yolov5detect/utils/torch_utils.py +0 -482
  93. bplusplus/yolov5detect/utils/triton.py +0 -90
  94. bplusplus-1.1.0.dist-info/METADATA +0 -179
  95. bplusplus-1.1.0.dist-info/RECORD +0 -92
  96. {bplusplus-1.1.0.dist-info → bplusplus-1.2.1.dist-info}/LICENSE +0 -0
  97. {bplusplus-1.1.0.dist-info → bplusplus-1.2.1.dist-info}/WHEEL +0 -0
@@ -1,797 +0,0 @@
1
- # Ultralytics YOLOv5 🚀, AGPL-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 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 (
31
- C3,
32
- SPP,
33
- SPPF,
34
- Bottleneck,
35
- BottleneckCSP,
36
- C3x,
37
- Concat,
38
- Conv,
39
- CrossConv,
40
- DWConv,
41
- DWConvTranspose2d,
42
- Focus,
43
- autopad,
44
- )
45
- from models.experimental import MixConv2d, attempt_load
46
- from models.yolo import Detect, Segment
47
- from utils.activations import SiLU
48
- from utils.general import LOGGER, make_divisible, print_args
49
-
50
-
51
- class TFBN(keras.layers.Layer):
52
- """TensorFlow BatchNormalization wrapper for initializing with optional pretrained weights."""
53
-
54
- def __init__(self, w=None):
55
- """Initializes a TensorFlow BatchNormalization layer with optional pretrained weights."""
56
- super().__init__()
57
- self.bn = keras.layers.BatchNormalization(
58
- beta_initializer=keras.initializers.Constant(w.bias.numpy()),
59
- gamma_initializer=keras.initializers.Constant(w.weight.numpy()),
60
- moving_mean_initializer=keras.initializers.Constant(w.running_mean.numpy()),
61
- moving_variance_initializer=keras.initializers.Constant(w.running_var.numpy()),
62
- epsilon=w.eps,
63
- )
64
-
65
- def call(self, inputs):
66
- """Applies batch normalization to the inputs."""
67
- return self.bn(inputs)
68
-
69
-
70
- class TFPad(keras.layers.Layer):
71
- """Pads input tensors in spatial dimensions 1 and 2 with specified integer or tuple padding values."""
72
-
73
- def __init__(self, pad):
74
- """
75
- Initializes a padding layer for spatial dimensions 1 and 2 with specified padding, supporting both int and tuple
76
- inputs.
77
-
78
- Inputs are
79
- """
80
- super().__init__()
81
- if isinstance(pad, int):
82
- self.pad = tf.constant([[0, 0], [pad, pad], [pad, pad], [0, 0]])
83
- else: # tuple/list
84
- self.pad = tf.constant([[0, 0], [pad[0], pad[0]], [pad[1], pad[1]], [0, 0]])
85
-
86
- def call(self, inputs):
87
- """Pads input tensor with zeros using specified padding, suitable for int and tuple pad dimensions."""
88
- return tf.pad(inputs, self.pad, mode="constant", constant_values=0)
89
-
90
-
91
- class TFConv(keras.layers.Layer):
92
- """Implements a standard convolutional layer with optional batch normalization and activation for TensorFlow."""
93
-
94
- def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
95
- """
96
- Initializes a standard convolution layer with optional batch normalization and activation; supports only
97
- group=1.
98
-
99
- Inputs are ch_in, ch_out, weights, kernel, stride, padding, groups.
100
- """
101
- super().__init__()
102
- assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
103
- # TensorFlow convolution padding is inconsistent with PyTorch (e.g. k=3 s=2 'SAME' padding)
104
- # see https://stackoverflow.com/questions/52975843/comparing-conv2d-with-padding-between-tensorflow-and-pytorch
105
- conv = keras.layers.Conv2D(
106
- filters=c2,
107
- kernel_size=k,
108
- strides=s,
109
- padding="SAME" if s == 1 else "VALID",
110
- use_bias=not hasattr(w, "bn"),
111
- kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
112
- bias_initializer="zeros" if hasattr(w, "bn") else keras.initializers.Constant(w.conv.bias.numpy()),
113
- )
114
- self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
115
- self.bn = TFBN(w.bn) if hasattr(w, "bn") else tf.identity
116
- self.act = activations(w.act) if act else tf.identity
117
-
118
- def call(self, inputs):
119
- """Applies convolution, batch normalization, and activation function to input tensors."""
120
- return self.act(self.bn(self.conv(inputs)))
121
-
122
-
123
- class TFDWConv(keras.layers.Layer):
124
- """Initializes a depthwise convolution layer with optional batch normalization and activation for TensorFlow."""
125
-
126
- def __init__(self, c1, c2, k=1, s=1, p=None, act=True, w=None):
127
- """
128
- Initializes a depthwise convolution layer with optional batch normalization and activation for TensorFlow
129
- models.
130
-
131
- Input are ch_in, ch_out, weights, kernel, stride, padding, groups.
132
- """
133
- super().__init__()
134
- assert c2 % c1 == 0, f"TFDWConv() output={c2} must be a multiple of input={c1} channels"
135
- conv = keras.layers.DepthwiseConv2D(
136
- kernel_size=k,
137
- depth_multiplier=c2 // c1,
138
- strides=s,
139
- padding="SAME" if s == 1 else "VALID",
140
- use_bias=not hasattr(w, "bn"),
141
- depthwise_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
142
- bias_initializer="zeros" if hasattr(w, "bn") else keras.initializers.Constant(w.conv.bias.numpy()),
143
- )
144
- self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
145
- self.bn = TFBN(w.bn) if hasattr(w, "bn") else tf.identity
146
- self.act = activations(w.act) if act else tf.identity
147
-
148
- def call(self, inputs):
149
- """Applies convolution, batch normalization, and activation function to input tensors."""
150
- return self.act(self.bn(self.conv(inputs)))
151
-
152
-
153
- class TFDWConvTranspose2d(keras.layers.Layer):
154
- """Implements a depthwise ConvTranspose2D layer for TensorFlow with specific settings."""
155
-
156
- def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0, w=None):
157
- """
158
- Initializes depthwise ConvTranspose2D layer with specific channel, kernel, stride, and padding settings.
159
-
160
- Inputs are ch_in, ch_out, weights, kernel, stride, padding, groups.
161
- """
162
- super().__init__()
163
- assert c1 == c2, f"TFDWConv() output={c2} must be equal to input={c1} channels"
164
- assert k == 4 and p1 == 1, "TFDWConv() only valid for k=4 and p1=1"
165
- weight, bias = w.weight.permute(2, 3, 1, 0).numpy(), w.bias.numpy()
166
- self.c1 = c1
167
- self.conv = [
168
- keras.layers.Conv2DTranspose(
169
- filters=1,
170
- kernel_size=k,
171
- strides=s,
172
- padding="VALID",
173
- output_padding=p2,
174
- use_bias=True,
175
- kernel_initializer=keras.initializers.Constant(weight[..., i : i + 1]),
176
- bias_initializer=keras.initializers.Constant(bias[i]),
177
- )
178
- for i in range(c1)
179
- ]
180
-
181
- def call(self, inputs):
182
- """Processes input through parallel convolutions and concatenates results, trimming border pixels."""
183
- return tf.concat([m(x) for m, x in zip(self.conv, tf.split(inputs, self.c1, 3))], 3)[:, 1:-1, 1:-1]
184
-
185
-
186
- class TFFocus(keras.layers.Layer):
187
- """Focuses spatial information into channel space using pixel shuffling and convolution for TensorFlow models."""
188
-
189
- def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
190
- """
191
- Initializes TFFocus layer to focus width and height information into channel space with custom convolution
192
- parameters.
193
-
194
- Inputs are ch_in, ch_out, kernel, stride, padding, groups.
195
- """
196
- super().__init__()
197
- self.conv = TFConv(c1 * 4, c2, k, s, p, g, act, w.conv)
198
-
199
- def call(self, inputs):
200
- """
201
- Performs pixel shuffling and convolution on input tensor, downsampling by 2 and expanding channels by 4.
202
-
203
- Example x(b,w,h,c) -> y(b,w/2,h/2,4c).
204
- """
205
- inputs = [inputs[:, ::2, ::2, :], inputs[:, 1::2, ::2, :], inputs[:, ::2, 1::2, :], inputs[:, 1::2, 1::2, :]]
206
- return self.conv(tf.concat(inputs, 3))
207
-
208
-
209
- class TFBottleneck(keras.layers.Layer):
210
- """Implements a TensorFlow bottleneck layer with optional shortcut connections for efficient feature extraction."""
211
-
212
- def __init__(self, c1, c2, shortcut=True, g=1, e=0.5, w=None):
213
- """
214
- Initializes a standard bottleneck layer for TensorFlow models, expanding and contracting channels with optional
215
- shortcut.
216
-
217
- Arguments are ch_in, ch_out, shortcut, groups, expansion.
218
- """
219
- super().__init__()
220
- c_ = int(c2 * e) # hidden channels
221
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
222
- self.cv2 = TFConv(c_, c2, 3, 1, g=g, w=w.cv2)
223
- self.add = shortcut and c1 == c2
224
-
225
- def call(self, inputs):
226
- """Performs forward pass; if shortcut is True & input/output channels match, adds input to the convolution
227
- result.
228
- """
229
- return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
230
-
231
-
232
- class TFCrossConv(keras.layers.Layer):
233
- """Implements a cross convolutional layer with optional expansion, grouping, and shortcut for TensorFlow."""
234
-
235
- def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False, w=None):
236
- """Initializes cross convolution layer with optional expansion, grouping, and shortcut addition capabilities."""
237
- super().__init__()
238
- c_ = int(c2 * e) # hidden channels
239
- self.cv1 = TFConv(c1, c_, (1, k), (1, s), w=w.cv1)
240
- self.cv2 = TFConv(c_, c2, (k, 1), (s, 1), g=g, w=w.cv2)
241
- self.add = shortcut and c1 == c2
242
-
243
- def call(self, inputs):
244
- """Passes input through two convolutions optionally adding the input if channel dimensions match."""
245
- return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
246
-
247
-
248
- class TFConv2d(keras.layers.Layer):
249
- """Implements a TensorFlow 2D convolution layer, mimicking PyTorch's nn.Conv2D for specified filters and stride."""
250
-
251
- def __init__(self, c1, c2, k, s=1, g=1, bias=True, w=None):
252
- """Initializes a TensorFlow 2D convolution layer, mimicking PyTorch's nn.Conv2D functionality for given filter
253
- sizes and stride.
254
- """
255
- super().__init__()
256
- assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
257
- self.conv = keras.layers.Conv2D(
258
- filters=c2,
259
- kernel_size=k,
260
- strides=s,
261
- padding="VALID",
262
- use_bias=bias,
263
- kernel_initializer=keras.initializers.Constant(w.weight.permute(2, 3, 1, 0).numpy()),
264
- bias_initializer=keras.initializers.Constant(w.bias.numpy()) if bias else None,
265
- )
266
-
267
- def call(self, inputs):
268
- """Applies a convolution operation to the inputs and returns the result."""
269
- return self.conv(inputs)
270
-
271
-
272
- class TFBottleneckCSP(keras.layers.Layer):
273
- """Implements a CSP bottleneck layer for TensorFlow models to enhance gradient flow and efficiency."""
274
-
275
- def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
276
- """
277
- Initializes CSP bottleneck layer with specified channel sizes, count, shortcut option, groups, and expansion
278
- ratio.
279
-
280
- Inputs are ch_in, ch_out, number, shortcut, groups, expansion.
281
- """
282
- super().__init__()
283
- c_ = int(c2 * e) # hidden channels
284
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
285
- self.cv2 = TFConv2d(c1, c_, 1, 1, bias=False, w=w.cv2)
286
- self.cv3 = TFConv2d(c_, c_, 1, 1, bias=False, w=w.cv3)
287
- self.cv4 = TFConv(2 * c_, c2, 1, 1, w=w.cv4)
288
- self.bn = TFBN(w.bn)
289
- self.act = lambda x: keras.activations.swish(x)
290
- self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
291
-
292
- def call(self, inputs):
293
- """Processes input through the model layers, concatenates, normalizes, activates, and reduces the output
294
- dimensions.
295
- """
296
- y1 = self.cv3(self.m(self.cv1(inputs)))
297
- y2 = self.cv2(inputs)
298
- return self.cv4(self.act(self.bn(tf.concat((y1, y2), axis=3))))
299
-
300
-
301
- class TFC3(keras.layers.Layer):
302
- """CSP bottleneck layer with 3 convolutions for TensorFlow, supporting optional shortcuts and group convolutions."""
303
-
304
- def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
305
- """
306
- Initializes CSP Bottleneck with 3 convolutions, supporting optional shortcuts and group convolutions.
307
-
308
- Inputs are ch_in, ch_out, number, shortcut, groups, expansion.
309
- """
310
- super().__init__()
311
- c_ = int(c2 * e) # hidden channels
312
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
313
- self.cv2 = TFConv(c1, c_, 1, 1, w=w.cv2)
314
- self.cv3 = TFConv(2 * c_, c2, 1, 1, w=w.cv3)
315
- self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
316
-
317
- def call(self, inputs):
318
- """
319
- Processes input through a sequence of transformations for object detection (YOLOv5).
320
-
321
- See https://github.com/ultralytics/yolov5.
322
- """
323
- return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
324
-
325
-
326
- class TFC3x(keras.layers.Layer):
327
- """A TensorFlow layer for enhanced feature extraction using cross-convolutions in object detection models."""
328
-
329
- def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
330
- """
331
- Initializes layer with cross-convolutions for enhanced feature extraction in object detection models.
332
-
333
- Inputs are ch_in, ch_out, number, shortcut, groups, expansion.
334
- """
335
- super().__init__()
336
- c_ = int(c2 * e) # hidden channels
337
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
338
- self.cv2 = TFConv(c1, c_, 1, 1, w=w.cv2)
339
- self.cv3 = TFConv(2 * c_, c2, 1, 1, w=w.cv3)
340
- self.m = keras.Sequential(
341
- [TFCrossConv(c_, c_, k=3, s=1, g=g, e=1.0, shortcut=shortcut, w=w.m[j]) for j in range(n)]
342
- )
343
-
344
- def call(self, inputs):
345
- """Processes input through cascaded convolutions and merges features, returning the final tensor output."""
346
- return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
347
-
348
-
349
- class TFSPP(keras.layers.Layer):
350
- """Implements spatial pyramid pooling for YOLOv3-SPP with specific channels and kernel sizes."""
351
-
352
- def __init__(self, c1, c2, k=(5, 9, 13), w=None):
353
- """Initializes a YOLOv3-SPP layer with specific input/output channels and kernel sizes for pooling."""
354
- super().__init__()
355
- c_ = c1 // 2 # hidden channels
356
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
357
- self.cv2 = TFConv(c_ * (len(k) + 1), c2, 1, 1, w=w.cv2)
358
- self.m = [keras.layers.MaxPool2D(pool_size=x, strides=1, padding="SAME") for x in k]
359
-
360
- def call(self, inputs):
361
- """Processes input through two TFConv layers and concatenates with max-pooled outputs at intermediate stage."""
362
- x = self.cv1(inputs)
363
- return self.cv2(tf.concat([x] + [m(x) for m in self.m], 3))
364
-
365
-
366
- class TFSPPF(keras.layers.Layer):
367
- """Implements a fast spatial pyramid pooling layer for TensorFlow with optimized feature extraction."""
368
-
369
- def __init__(self, c1, c2, k=5, w=None):
370
- """Initializes a fast spatial pyramid pooling layer with customizable in/out channels, kernel size, and
371
- weights.
372
- """
373
- super().__init__()
374
- c_ = c1 // 2 # hidden channels
375
- self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
376
- self.cv2 = TFConv(c_ * 4, c2, 1, 1, w=w.cv2)
377
- self.m = keras.layers.MaxPool2D(pool_size=k, strides=1, padding="SAME")
378
-
379
- def call(self, inputs):
380
- """Executes the model's forward pass, concatenating input features with three max-pooled versions before final
381
- convolution.
382
- """
383
- x = self.cv1(inputs)
384
- y1 = self.m(x)
385
- y2 = self.m(y1)
386
- return self.cv2(tf.concat([x, y1, y2, self.m(y2)], 3))
387
-
388
-
389
- class TFDetect(keras.layers.Layer):
390
- """Implements YOLOv5 object detection layer in TensorFlow for predicting bounding boxes and class probabilities."""
391
-
392
- def __init__(self, nc=80, anchors=(), ch=(), imgsz=(640, 640), w=None):
393
- """Initializes YOLOv5 detection layer for TensorFlow with configurable classes, anchors, channels, and image
394
- size.
395
- """
396
- super().__init__()
397
- self.stride = tf.convert_to_tensor(w.stride.numpy(), dtype=tf.float32)
398
- self.nc = nc # number of classes
399
- self.no = nc + 5 # number of outputs per anchor
400
- self.nl = len(anchors) # number of detection layers
401
- self.na = len(anchors[0]) // 2 # number of anchors
402
- self.grid = [tf.zeros(1)] * self.nl # init grid
403
- self.anchors = tf.convert_to_tensor(w.anchors.numpy(), dtype=tf.float32)
404
- self.anchor_grid = tf.reshape(self.anchors * tf.reshape(self.stride, [self.nl, 1, 1]), [self.nl, 1, -1, 1, 2])
405
- self.m = [TFConv2d(x, self.no * self.na, 1, w=w.m[i]) for i, x in enumerate(ch)]
406
- self.training = False # set to False after building model
407
- self.imgsz = imgsz
408
- for i in range(self.nl):
409
- ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
410
- self.grid[i] = self._make_grid(nx, ny)
411
-
412
- def call(self, inputs):
413
- """Performs forward pass through the model layers to predict object bounding boxes and classifications."""
414
- z = [] # inference output
415
- x = []
416
- for i in range(self.nl):
417
- x.append(self.m[i](inputs[i]))
418
- # x(bs,20,20,255) to x(bs,3,20,20,85)
419
- ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
420
- x[i] = tf.reshape(x[i], [-1, ny * nx, self.na, self.no])
421
-
422
- if not self.training: # inference
423
- y = x[i]
424
- grid = tf.transpose(self.grid[i], [0, 2, 1, 3]) - 0.5
425
- anchor_grid = tf.transpose(self.anchor_grid[i], [0, 2, 1, 3]) * 4
426
- xy = (tf.sigmoid(y[..., 0:2]) * 2 + grid) * self.stride[i] # xy
427
- wh = tf.sigmoid(y[..., 2:4]) ** 2 * anchor_grid
428
- # Normalize xywh to 0-1 to reduce calibration error
429
- xy /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
430
- wh /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
431
- y = tf.concat([xy, wh, tf.sigmoid(y[..., 4 : 5 + self.nc]), y[..., 5 + self.nc :]], -1)
432
- z.append(tf.reshape(y, [-1, self.na * ny * nx, self.no]))
433
-
434
- return tf.transpose(x, [0, 2, 1, 3]) if self.training else (tf.concat(z, 1),)
435
-
436
- @staticmethod
437
- def _make_grid(nx=20, ny=20):
438
- """Generates a 2D grid of coordinates in (x, y) format with shape [1, 1, ny*nx, 2]."""
439
- # return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
440
- xv, yv = tf.meshgrid(tf.range(nx), tf.range(ny))
441
- return tf.cast(tf.reshape(tf.stack([xv, yv], 2), [1, 1, ny * nx, 2]), dtype=tf.float32)
442
-
443
-
444
- class TFSegment(TFDetect):
445
- """YOLOv5 segmentation head for TensorFlow, combining detection and segmentation."""
446
-
447
- def __init__(self, nc=80, anchors=(), nm=32, npr=256, ch=(), imgsz=(640, 640), w=None):
448
- """Initializes YOLOv5 Segment head with specified channel depths, anchors, and input size for segmentation
449
- models.
450
- """
451
- super().__init__(nc, anchors, ch, imgsz, w)
452
- self.nm = nm # number of masks
453
- self.npr = npr # number of protos
454
- self.no = 5 + nc + self.nm # number of outputs per anchor
455
- self.m = [TFConv2d(x, self.no * self.na, 1, w=w.m[i]) for i, x in enumerate(ch)] # output conv
456
- self.proto = TFProto(ch[0], self.npr, self.nm, w=w.proto) # protos
457
- self.detect = TFDetect.call
458
-
459
- def call(self, x):
460
- """Applies detection and proto layers on input, returning detections and optionally protos if training."""
461
- p = self.proto(x[0])
462
- # p = TFUpsample(None, scale_factor=4, mode='nearest')(self.proto(x[0])) # (optional) full-size protos
463
- p = tf.transpose(p, [0, 3, 1, 2]) # from shape(1,160,160,32) to shape(1,32,160,160)
464
- x = self.detect(self, x)
465
- return (x, p) if self.training else (x[0], p)
466
-
467
-
468
- class TFProto(keras.layers.Layer):
469
- """Implements convolutional and upsampling layers for feature extraction in YOLOv5 segmentation."""
470
-
471
- def __init__(self, c1, c_=256, c2=32, w=None):
472
- """Initializes TFProto layer with convolutional and upsampling layers for feature extraction and
473
- transformation.
474
- """
475
- super().__init__()
476
- self.cv1 = TFConv(c1, c_, k=3, w=w.cv1)
477
- self.upsample = TFUpsample(None, scale_factor=2, mode="nearest")
478
- self.cv2 = TFConv(c_, c_, k=3, w=w.cv2)
479
- self.cv3 = TFConv(c_, c2, w=w.cv3)
480
-
481
- def call(self, inputs):
482
- """Performs forward pass through the model, applying convolutions and upscaling on input tensor."""
483
- return self.cv3(self.cv2(self.upsample(self.cv1(inputs))))
484
-
485
-
486
- class TFUpsample(keras.layers.Layer):
487
- """Implements a TensorFlow upsampling layer with specified size, scale factor, and interpolation mode."""
488
-
489
- def __init__(self, size, scale_factor, mode, w=None):
490
- """
491
- Initializes a TensorFlow upsampling layer with specified size, scale_factor, and mode, ensuring scale_factor is
492
- even.
493
-
494
- Warning: all arguments needed including 'w'
495
- """
496
- super().__init__()
497
- assert scale_factor % 2 == 0, "scale_factor must be multiple of 2"
498
- self.upsample = lambda x: tf.image.resize(x, (x.shape[1] * scale_factor, x.shape[2] * scale_factor), mode)
499
- # self.upsample = keras.layers.UpSampling2D(size=scale_factor, interpolation=mode)
500
- # with default arguments: align_corners=False, half_pixel_centers=False
501
- # self.upsample = lambda x: tf.raw_ops.ResizeNearestNeighbor(images=x,
502
- # size=(x.shape[1] * 2, x.shape[2] * 2))
503
-
504
- def call(self, inputs):
505
- """Applies upsample operation to inputs using nearest neighbor interpolation."""
506
- return self.upsample(inputs)
507
-
508
-
509
- class TFConcat(keras.layers.Layer):
510
- """Implements TensorFlow's version of torch.concat() for concatenating tensors along the last dimension."""
511
-
512
- def __init__(self, dimension=1, w=None):
513
- """Initializes a TensorFlow layer for NCHW to NHWC concatenation, requiring dimension=1."""
514
- super().__init__()
515
- assert dimension == 1, "convert only NCHW to NHWC concat"
516
- self.d = 3
517
-
518
- def call(self, inputs):
519
- """Concatenates a list of tensors along the last dimension, used for NCHW to NHWC conversion."""
520
- return tf.concat(inputs, self.d)
521
-
522
-
523
- def parse_model(d, ch, model, imgsz):
524
- """Parses a model definition dict `d` to create YOLOv5 model layers, including dynamic channel adjustments."""
525
- LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}")
526
- anchors, nc, gd, gw, ch_mul = (
527
- d["anchors"],
528
- d["nc"],
529
- d["depth_multiple"],
530
- d["width_multiple"],
531
- d.get("channel_multiple"),
532
- )
533
- na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
534
- no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
535
- if not ch_mul:
536
- ch_mul = 8
537
-
538
- layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
539
- for i, (f, n, m, args) in enumerate(d["backbone"] + d["head"]): # from, number, module, args
540
- m_str = m
541
- m = eval(m) if isinstance(m, str) else m # eval strings
542
- for j, a in enumerate(args):
543
- try:
544
- args[j] = eval(a) if isinstance(a, str) else a # eval strings
545
- except NameError:
546
- pass
547
-
548
- n = max(round(n * gd), 1) if n > 1 else n # depth gain
549
- if m in [
550
- nn.Conv2d,
551
- Conv,
552
- DWConv,
553
- DWConvTranspose2d,
554
- Bottleneck,
555
- SPP,
556
- SPPF,
557
- MixConv2d,
558
- Focus,
559
- CrossConv,
560
- BottleneckCSP,
561
- C3,
562
- C3x,
563
- ]:
564
- c1, c2 = ch[f], args[0]
565
- c2 = make_divisible(c2 * gw, ch_mul) if c2 != no else c2
566
-
567
- args = [c1, c2, *args[1:]]
568
- if m in [BottleneckCSP, C3, C3x]:
569
- args.insert(2, n)
570
- n = 1
571
- elif m is nn.BatchNorm2d:
572
- args = [ch[f]]
573
- elif m is Concat:
574
- c2 = sum(ch[-1 if x == -1 else x + 1] for x in f)
575
- elif m in [Detect, Segment]:
576
- args.append([ch[x + 1] for x in f])
577
- if isinstance(args[1], int): # number of anchors
578
- args[1] = [list(range(args[1] * 2))] * len(f)
579
- if m is Segment:
580
- args[3] = make_divisible(args[3] * gw, ch_mul)
581
- args.append(imgsz)
582
- else:
583
- c2 = ch[f]
584
-
585
- tf_m = eval("TF" + m_str.replace("nn.", ""))
586
- m_ = (
587
- keras.Sequential([tf_m(*args, w=model.model[i][j]) for j in range(n)])
588
- if n > 1
589
- else tf_m(*args, w=model.model[i])
590
- ) # module
591
-
592
- torch_m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
593
- t = str(m)[8:-2].replace("__main__.", "") # module type
594
- np = sum(x.numel() for x in torch_m_.parameters()) # number params
595
- m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params
596
- LOGGER.info(f"{i:>3}{str(f):>18}{str(n):>3}{np:>10} {t:<40}{str(args):<30}") # print
597
- save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
598
- layers.append(m_)
599
- ch.append(c2)
600
- return keras.Sequential(layers), sorted(save)
601
-
602
-
603
- class TFModel:
604
- """Implements YOLOv5 model in TensorFlow, supporting TensorFlow, Keras, and TFLite formats for object detection."""
605
-
606
- def __init__(self, cfg="yolov5s.yaml", ch=3, nc=None, model=None, imgsz=(640, 640)):
607
- """Initializes TF YOLOv5 model with specified configuration, channels, classes, model instance, and input
608
- size.
609
- """
610
- super().__init__()
611
- if isinstance(cfg, dict):
612
- self.yaml = cfg # model dict
613
- else: # is *.yaml
614
- import yaml # for torch hub
615
-
616
- self.yaml_file = Path(cfg).name
617
- with open(cfg) as f:
618
- self.yaml = yaml.load(f, Loader=yaml.FullLoader) # model dict
619
-
620
- # Define model
621
- if nc and nc != self.yaml["nc"]:
622
- LOGGER.info(f"Overriding {cfg} nc={self.yaml['nc']} with nc={nc}")
623
- self.yaml["nc"] = nc # override yaml value
624
- self.model, self.savelist = parse_model(deepcopy(self.yaml), ch=[ch], model=model, imgsz=imgsz)
625
-
626
- def predict(
627
- self,
628
- inputs,
629
- tf_nms=False,
630
- agnostic_nms=False,
631
- topk_per_class=100,
632
- topk_all=100,
633
- iou_thres=0.45,
634
- conf_thres=0.25,
635
- ):
636
- """Runs inference on input data, with an option for TensorFlow NMS."""
637
- y = [] # outputs
638
- x = inputs
639
- for m in self.model.layers:
640
- if m.f != -1: # if not from previous layer
641
- 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
642
-
643
- x = m(x) # run
644
- y.append(x if m.i in self.savelist else None) # save output
645
-
646
- # Add TensorFlow NMS
647
- if tf_nms:
648
- boxes = self._xywh2xyxy(x[0][..., :4])
649
- probs = x[0][:, :, 4:5]
650
- classes = x[0][:, :, 5:]
651
- scores = probs * classes
652
- if agnostic_nms:
653
- nms = AgnosticNMS()((boxes, classes, scores), topk_all, iou_thres, conf_thres)
654
- else:
655
- boxes = tf.expand_dims(boxes, 2)
656
- nms = tf.image.combined_non_max_suppression(
657
- boxes, scores, topk_per_class, topk_all, iou_thres, conf_thres, clip_boxes=False
658
- )
659
- return (nms,)
660
- return x # output [1,6300,85] = [xywh, conf, class0, class1, ...]
661
- # x = x[0] # [x(1,6300,85), ...] to x(6300,85)
662
- # xywh = x[..., :4] # x(6300,4) boxes
663
- # conf = x[..., 4:5] # x(6300,1) confidences
664
- # cls = tf.reshape(tf.cast(tf.argmax(x[..., 5:], axis=1), tf.float32), (-1, 1)) # x(6300,1) classes
665
- # return tf.concat([conf, cls, xywh], 1)
666
-
667
- @staticmethod
668
- def _xywh2xyxy(xywh):
669
- """Converts bounding box format from [x, y, w, h] to [x1, y1, x2, y2], where xy1=top-left and xy2=bottom-
670
- right.
671
- """
672
- x, y, w, h = tf.split(xywh, num_or_size_splits=4, axis=-1)
673
- return tf.concat([x - w / 2, y - h / 2, x + w / 2, y + h / 2], axis=-1)
674
-
675
-
676
- class AgnosticNMS(keras.layers.Layer):
677
- """Performs agnostic non-maximum suppression (NMS) on detected objects using IoU and confidence thresholds."""
678
-
679
- def call(self, input, topk_all, iou_thres, conf_thres):
680
- """Performs agnostic NMS on input tensors using given thresholds and top-K selection."""
681
- return tf.map_fn(
682
- lambda x: self._nms(x, topk_all, iou_thres, conf_thres),
683
- input,
684
- fn_output_signature=(tf.float32, tf.float32, tf.float32, tf.int32),
685
- name="agnostic_nms",
686
- )
687
-
688
- @staticmethod
689
- def _nms(x, topk_all=100, iou_thres=0.45, conf_thres=0.25):
690
- """Performs agnostic non-maximum suppression (NMS) on detected objects, filtering based on IoU and confidence
691
- thresholds.
692
- """
693
- boxes, classes, scores = x
694
- class_inds = tf.cast(tf.argmax(classes, axis=-1), tf.float32)
695
- scores_inp = tf.reduce_max(scores, -1)
696
- selected_inds = tf.image.non_max_suppression(
697
- boxes, scores_inp, max_output_size=topk_all, iou_threshold=iou_thres, score_threshold=conf_thres
698
- )
699
- selected_boxes = tf.gather(boxes, selected_inds)
700
- padded_boxes = tf.pad(
701
- selected_boxes,
702
- paddings=[[0, topk_all - tf.shape(selected_boxes)[0]], [0, 0]],
703
- mode="CONSTANT",
704
- constant_values=0.0,
705
- )
706
- selected_scores = tf.gather(scores_inp, selected_inds)
707
- padded_scores = tf.pad(
708
- selected_scores,
709
- paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
710
- mode="CONSTANT",
711
- constant_values=-1.0,
712
- )
713
- selected_classes = tf.gather(class_inds, selected_inds)
714
- padded_classes = tf.pad(
715
- selected_classes,
716
- paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
717
- mode="CONSTANT",
718
- constant_values=-1.0,
719
- )
720
- valid_detections = tf.shape(selected_inds)[0]
721
- return padded_boxes, padded_scores, padded_classes, valid_detections
722
-
723
-
724
- def activations(act=nn.SiLU):
725
- """Converts PyTorch activations to TensorFlow equivalents, supporting LeakyReLU, Hardswish, and SiLU/Swish."""
726
- if isinstance(act, nn.LeakyReLU):
727
- return lambda x: keras.activations.relu(x, alpha=0.1)
728
- elif isinstance(act, nn.Hardswish):
729
- return lambda x: x * tf.nn.relu6(x + 3) * 0.166666667
730
- elif isinstance(act, (nn.SiLU, SiLU)):
731
- return lambda x: keras.activations.swish(x)
732
- else:
733
- raise Exception(f"no matching TensorFlow activation found for PyTorch activation {act}")
734
-
735
-
736
- def representative_dataset_gen(dataset, ncalib=100):
737
- """Generates a representative dataset for calibration by yielding transformed numpy arrays from the input
738
- dataset.
739
- """
740
- for n, (path, img, im0s, vid_cap, string) in enumerate(dataset):
741
- im = np.transpose(img, [1, 2, 0])
742
- im = np.expand_dims(im, axis=0).astype(np.float32)
743
- im /= 255
744
- yield [im]
745
- if n >= ncalib:
746
- break
747
-
748
-
749
- def run(
750
- weights=ROOT / "yolov5s.pt", # weights path
751
- imgsz=(640, 640), # inference size h,w
752
- batch_size=1, # batch size
753
- dynamic=False, # dynamic batch size
754
- ):
755
- # PyTorch model
756
- """Exports YOLOv5 model from PyTorch to TensorFlow and Keras formats, performing inference for validation."""
757
- im = torch.zeros((batch_size, 3, *imgsz)) # BCHW image
758
- model = attempt_load(weights, device=torch.device("cpu"), inplace=True, fuse=False)
759
- _ = model(im) # inference
760
- model.info()
761
-
762
- # TensorFlow model
763
- im = tf.zeros((batch_size, *imgsz, 3)) # BHWC image
764
- tf_model = TFModel(cfg=model.yaml, model=model, nc=model.nc, imgsz=imgsz)
765
- _ = tf_model.predict(im) # inference
766
-
767
- # Keras model
768
- im = keras.Input(shape=(*imgsz, 3), batch_size=None if dynamic else batch_size)
769
- keras_model = keras.Model(inputs=im, outputs=tf_model.predict(im))
770
- keras_model.summary()
771
-
772
- LOGGER.info("PyTorch, TensorFlow and Keras models successfully verified.\nUse export.py for TF model export.")
773
-
774
-
775
- def parse_opt():
776
- """Parses and returns command-line options for model inference, including weights path, image size, batch size, and
777
- dynamic batching.
778
- """
779
- parser = argparse.ArgumentParser()
780
- parser.add_argument("--weights", type=str, default=ROOT / "yolov5s.pt", help="weights path")
781
- parser.add_argument("--imgsz", "--img", "--img-size", nargs="+", type=int, default=[640], help="inference size h,w")
782
- parser.add_argument("--batch-size", type=int, default=1, help="batch size")
783
- parser.add_argument("--dynamic", action="store_true", help="dynamic batch size")
784
- opt = parser.parse_args()
785
- opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
786
- print_args(vars(opt))
787
- return opt
788
-
789
-
790
- def main(opt):
791
- """Executes the YOLOv5 model run function with parsed command line options."""
792
- run(**vars(opt))
793
-
794
-
795
- if __name__ == "__main__":
796
- opt = parse_opt()
797
- main(opt)