keras-hub-nightly 0.19.0.dev202502060348__py3-none-any.whl → 0.19.0.dev202502080344__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.
Files changed (28) hide show
  1. keras_hub/api/__init__.py +0 -1
  2. keras_hub/api/layers/__init__.py +3 -1
  3. keras_hub/api/models/__init__.py +10 -4
  4. keras_hub/src/{models/retinanet → layers/modeling}/anchor_generator.py +11 -18
  5. keras_hub/src/{models/retinanet → layers/modeling}/box_matcher.py +17 -4
  6. keras_hub/src/{models/retinanet → layers/modeling}/non_max_supression.py +84 -32
  7. keras_hub/src/layers/preprocessing/image_converter.py +25 -3
  8. keras_hub/src/models/{image_object_detector.py → object_detector.py} +12 -7
  9. keras_hub/src/models/{image_object_detector_preprocessor.py → object_detector_preprocessor.py} +29 -13
  10. keras_hub/src/models/retinanet/retinanet_image_converter.py +8 -40
  11. keras_hub/src/models/retinanet/retinanet_label_encoder.py +18 -16
  12. keras_hub/src/models/retinanet/retinanet_object_detector.py +28 -28
  13. keras_hub/src/models/retinanet/retinanet_object_detector_preprocessor.py +3 -3
  14. keras_hub/src/utils/tensor_utils.py +13 -0
  15. keras_hub/src/version_utils.py +1 -1
  16. {keras_hub_nightly-0.19.0.dev202502060348.dist-info → keras_hub_nightly-0.19.0.dev202502080344.dist-info}/METADATA +1 -1
  17. {keras_hub_nightly-0.19.0.dev202502060348.dist-info → keras_hub_nightly-0.19.0.dev202502080344.dist-info}/RECORD +19 -28
  18. keras_hub/api/bounding_box/__init__.py +0 -23
  19. keras_hub/src/bounding_box/__init__.py +0 -2
  20. keras_hub/src/bounding_box/converters.py +0 -606
  21. keras_hub/src/bounding_box/formats.py +0 -149
  22. keras_hub/src/bounding_box/iou.py +0 -251
  23. keras_hub/src/bounding_box/to_dense.py +0 -81
  24. keras_hub/src/bounding_box/to_ragged.py +0 -86
  25. keras_hub/src/bounding_box/utils.py +0 -181
  26. keras_hub/src/bounding_box/validate_format.py +0 -85
  27. {keras_hub_nightly-0.19.0.dev202502060348.dist-info → keras_hub_nightly-0.19.0.dev202502080344.dist-info}/WHEEL +0 -0
  28. {keras_hub_nightly-0.19.0.dev202502060348.dist-info → keras_hub_nightly-0.19.0.dev202502080344.dist-info}/top_level.txt +0 -0
keras_hub/api/__init__.py CHANGED
@@ -4,7 +4,6 @@ This file was autogenerated. Do not edit it by hand,
4
4
  since your modifications would be overwritten.
5
5
  """
6
6
 
7
- from keras_hub.api import bounding_box
8
7
  from keras_hub.api import layers
9
8
  from keras_hub.api import metrics
10
9
  from keras_hub.api import models
@@ -5,11 +5,14 @@ since your modifications would be overwritten.
5
5
  """
6
6
 
7
7
  from keras_hub.src.layers.modeling.alibi_bias import AlibiBias
8
+ from keras_hub.src.layers.modeling.anchor_generator import AnchorGenerator
9
+ from keras_hub.src.layers.modeling.box_matcher import BoxMatcher
8
10
  from keras_hub.src.layers.modeling.cached_multi_head_attention import (
9
11
  CachedMultiHeadAttention,
10
12
  )
11
13
  from keras_hub.src.layers.modeling.f_net_encoder import FNetEncoder
12
14
  from keras_hub.src.layers.modeling.masked_lm_head import MaskedLMHead
15
+ from keras_hub.src.layers.modeling.non_max_supression import NonMaxSuppression
13
16
  from keras_hub.src.layers.modeling.position_embedding import PositionEmbedding
14
17
  from keras_hub.src.layers.modeling.reversible_embedding import (
15
18
  ReversibleEmbedding,
@@ -55,7 +58,6 @@ from keras_hub.src.models.pali_gemma.pali_gemma_image_converter import (
55
58
  from keras_hub.src.models.resnet.resnet_image_converter import (
56
59
  ResNetImageConverter,
57
60
  )
58
- from keras_hub.src.models.retinanet.anchor_generator import AnchorGenerator
59
61
  from keras_hub.src.models.retinanet.retinanet_image_converter import (
60
62
  RetinaNetImageConverter,
61
63
  )
@@ -193,10 +193,6 @@ from keras_hub.src.models.image_classifier import ImageClassifier
193
193
  from keras_hub.src.models.image_classifier_preprocessor import (
194
194
  ImageClassifierPreprocessor,
195
195
  )
196
- from keras_hub.src.models.image_object_detector import ImageObjectDetector
197
- from keras_hub.src.models.image_object_detector_preprocessor import (
198
- ImageObjectDetectorPreprocessor,
199
- )
200
196
  from keras_hub.src.models.image_segmenter import ImageSegmenter
201
197
  from keras_hub.src.models.image_segmenter_preprocessor import (
202
198
  ImageSegmenterPreprocessor,
@@ -232,6 +228,16 @@ from keras_hub.src.models.mobilenet.mobilenet_backbone import MobileNetBackbone
232
228
  from keras_hub.src.models.mobilenet.mobilenet_image_classifier import (
233
229
  MobileNetImageClassifier,
234
230
  )
231
+ from keras_hub.src.models.object_detector import ObjectDetector
232
+ from keras_hub.src.models.object_detector import (
233
+ ObjectDetector as ImageObjectDetector,
234
+ )
235
+ from keras_hub.src.models.object_detector_preprocessor import (
236
+ ObjectDetectorPreprocessor,
237
+ )
238
+ from keras_hub.src.models.object_detector_preprocessor import (
239
+ ObjectDetectorPreprocessor as ImageObjectDetectorPreprocessor,
240
+ )
235
241
  from keras_hub.src.models.opt.opt_backbone import OPTBackbone
236
242
  from keras_hub.src.models.opt.opt_causal_lm import OPTCausalLM
237
243
  from keras_hub.src.models.opt.opt_causal_lm_preprocessor import (
@@ -4,9 +4,7 @@ import keras
4
4
  from keras import ops
5
5
 
6
6
  from keras_hub.src.api_export import keras_hub_export
7
-
8
- # TODO: https://github.com/keras-team/keras-hub/issues/1965
9
- from keras_hub.src.bounding_box.converters import convert_format
7
+ from keras_hub.src.utils.tensor_utils import assert_bounding_box_support
10
8
 
11
9
 
12
10
  @keras_hub_export("keras_hub.layers.AnchorGenerator")
@@ -56,7 +54,7 @@ class AnchorGenerator(keras.layers.Layer):
56
54
 
57
55
  Example:
58
56
  ```python
59
- anchor_generator = AnchorGenerator(
57
+ anchor_generator = keras_hub.layers.AnchorGenerator(
60
58
  bounding_box_format='xyxy',
61
59
  min_level=3,
62
60
  max_level=7,
@@ -78,6 +76,9 @@ class AnchorGenerator(keras.layers.Layer):
78
76
  anchor_size,
79
77
  **kwargs,
80
78
  ):
79
+ # Check whether current version of keras support bounding box utils
80
+ assert_bounding_box_support(self.__class__.__name__)
81
+
81
82
  super().__init__(**kwargs)
82
83
  self.bounding_box_format = bounding_box_format
83
84
  self.min_level = min_level
@@ -94,29 +95,23 @@ class AnchorGenerator(keras.layers.Layer):
94
95
  image_shape = images_shape[1:-1]
95
96
  else:
96
97
  image_shape = images_shape[:-1]
97
-
98
98
  image_shape = tuple(image_shape)
99
-
100
99
  multilevel_anchors = {}
101
100
  for level in range(self.min_level, self.max_level + 1):
102
101
  # Calculate the feature map size for this level
103
102
  feat_size_y = math.ceil(image_shape[0] / 2**level)
104
103
  feat_size_x = math.ceil(image_shape[1] / 2**level)
105
-
106
104
  # Calculate the stride (step size) for this level
107
105
  stride_y = image_shape[0] // feat_size_y
108
106
  stride_x = image_shape[1] // feat_size_x
109
-
110
107
  # Generate anchor center points
111
108
  # Start from stride/2 to center anchors on pixels
112
109
  cx = ops.arange(0, feat_size_x, dtype="float32") * stride_x
113
110
  cy = ops.arange(0, feat_size_y, dtype="float32") * stride_y
114
-
115
111
  # Create a grid of anchor centers
116
112
  cy_grid, cx_grid = ops.meshgrid(cy, cx, indexing="ij")
117
113
  cy_grid = ops.reshape(cy_grid, (-1,))
118
114
  cx_grid = ops.reshape(cx_grid, (-1,))
119
-
120
115
  shifts = ops.stack((cx_grid, cy_grid, cx_grid, cy_grid), axis=1)
121
116
  sizes = [
122
117
  int(
@@ -124,7 +119,6 @@ class AnchorGenerator(keras.layers.Layer):
124
119
  )
125
120
  for scale in range(self.num_scales)
126
121
  ]
127
-
128
122
  base_anchors = self.generate_base_anchors(
129
123
  sizes=sizes, aspect_ratios=self.aspect_ratios
130
124
  )
@@ -133,10 +127,12 @@ class AnchorGenerator(keras.layers.Layer):
133
127
 
134
128
  anchors = shifts + base_anchors
135
129
  anchors = ops.reshape(anchors, (-1, 4))
136
- multilevel_anchors[f"P{level}"] = convert_format(
137
- anchors,
138
- source="xyxy",
139
- target=self.bounding_box_format,
130
+ multilevel_anchors[f"P{level}"] = (
131
+ keras.utils.bounding_boxes.convert_format(
132
+ anchors,
133
+ source="xyxy",
134
+ target=self.bounding_box_format,
135
+ )
140
136
  )
141
137
  return multilevel_anchors
142
138
 
@@ -145,10 +141,8 @@ class AnchorGenerator(keras.layers.Layer):
145
141
  aspect_ratios = ops.convert_to_tensor(aspect_ratios)
146
142
  h_ratios = ops.sqrt(aspect_ratios)
147
143
  w_ratios = 1 / h_ratios
148
-
149
144
  ws = ops.reshape(w_ratios[:, None] * sizes[None, :], (-1,))
150
145
  hs = ops.reshape(h_ratios[:, None] * sizes[None, :], (-1,))
151
-
152
146
  base_anchors = ops.stack([-1 * ws, -1 * hs, ws, hs], axis=1) / 2
153
147
  base_anchors = ops.round(base_anchors)
154
148
  return base_anchors
@@ -159,7 +153,6 @@ class AnchorGenerator(keras.layers.Layer):
159
153
  image_height, image_width = input_shape[1:-1]
160
154
  else:
161
155
  image_height, image_width = input_shape[:-1]
162
-
163
156
  for i in range(self.min_level, self.max_level + 1):
164
157
  multilevel_boxes_shape[f"P{i}"] = (
165
158
  int(
@@ -1,7 +1,11 @@
1
1
  import keras
2
2
  from keras import ops
3
3
 
4
+ from keras_hub.src.api_export import keras_hub_export
5
+ from keras_hub.src.utils.tensor_utils import assert_bounding_box_support
4
6
 
7
+
8
+ @keras_hub_export("keras_hub.layers.BoxMatcher")
5
9
  class BoxMatcher(keras.layers.Layer):
6
10
  """Box matching logic based on argmax of highest value (e.g., IOU).
7
11
 
@@ -51,10 +55,16 @@ class BoxMatcher(keras.layers.Layer):
51
55
 
52
56
  Example:
53
57
  ```python
54
- box_matcher = keras_cv.layers.BoxMatcher([0.3, 0.7], [-1, 0, 1])
55
- iou_metric = keras_cv.bounding_box.compute_iou(anchors, boxes)
56
- matched_columns, matched_match_values = box_matcher(iou_metric)
57
- cls_mask = ops.less_equal(matched_match_values, 0)
58
+ positive_threshold = 0.5
59
+ negative_threshold = 0.4
60
+
61
+ matcher = keras_hub.layers.BoxMatcher(
62
+ thresholds=[negative_threshold, positive_threshold],
63
+ match_values=[-1, -2, 1],
64
+ )
65
+ match_indices, matched_values = matcher(sim_matrix)
66
+ positive_mask = ops.equal(matched_vals, 1)
67
+ ignore_mask = ops.equal(matched_vals, -2)
58
68
  ```
59
69
 
60
70
  """
@@ -66,6 +76,9 @@ class BoxMatcher(keras.layers.Layer):
66
76
  force_match_for_each_col=False,
67
77
  **kwargs,
68
78
  ):
79
+ # Check whether current version of keras support bounding box utils
80
+ assert_bounding_box_support(self.__class__.__name__)
81
+
69
82
  super().__init__(**kwargs)
70
83
  if sorted(thresholds) != thresholds:
71
84
  raise ValueError(f"`threshold` must be sorted, got {thresholds}")
@@ -3,32 +3,52 @@ import math
3
3
  import keras
4
4
  from keras import ops
5
5
 
6
- # TODO: https://github.com/keras-team/keras-hub/issues/1965
7
- from keras_hub.src.bounding_box import converters
8
- from keras_hub.src.bounding_box import utils
9
- from keras_hub.src.bounding_box import validate_format
6
+ from keras_hub.src.api_export import keras_hub_export
7
+ from keras_hub.src.utils.tensor_utils import assert_bounding_box_support
10
8
 
11
9
  EPSILON = 1e-8
12
10
 
13
11
 
12
+ @keras_hub_export("keras_hub.layers.NonMaxSuppression")
14
13
  class NonMaxSuppression(keras.layers.Layer):
15
14
  """A Keras layer that decodes predictions of an object detection model.
16
15
 
17
16
  Args:
18
- bounding_box_format: The format of bounding boxes of input dataset.
19
- Refer
20
- TODO: link keras core bounding box docs
21
- for more details on supported bounding box formats.
17
+ bounding_box_format: str. The format of bounding boxes of input dataset.
18
+ Refer `keras.utils.bounding_boxes.convert_format` args for more
19
+ details on supported bounding box formats.
22
20
  from_logits: boolean, True means input score is logits, False means
23
21
  confidence.
24
- iou_threshold: a float value in the range [0, 1] representing the
22
+ iou_threshold: float. Value in the range [0, 1] representing the
25
23
  minimum IoU threshold for two boxes to be considered
26
24
  same for suppression. Defaults to 0.5.
27
- confidence_threshold: a float value in the range [0, 1]. All boxes with
25
+ confidence_threshold: float. Value in the range [0, 1]. All boxes with
28
26
  confidence below this value will be discarded, defaults to 0.5.
29
- max_detections: the maximum detections to consider after nms is applied.
30
- A large number may trigger significant memory overhead,
27
+ max_detections: int. the maximum detections to consider after nms is
28
+ applied. A large number may trigger significant memory overhead,
31
29
  defaults to 100.
30
+
31
+ Example:
32
+ ```python
33
+ boxes = np.random.uniform(low=0, high=1, size=(2, 5, 4))
34
+ classes = np.expand_dims(
35
+ np.array(
36
+ [[0.1, 0.1, 0.4, 0.5, 0.9], [0.7, 0.5, 0.3, 0.0, 0.0]],
37
+ "float32",
38
+ ),
39
+ axis=-1,
40
+ )
41
+
42
+ nms = keras_hub.layers.NonMaxSuppression(
43
+ bounding_box_format="yxyx",
44
+ from_logits=False,
45
+ iou_threshold=1.0,
46
+ confidence_threshold=0.1,
47
+ max_detections=1,
48
+ )
49
+
50
+ nms_outputs = nms(boxes, classes)
51
+ ```
32
52
  """
33
53
 
34
54
  def __init__(
@@ -40,6 +60,8 @@ class NonMaxSuppression(keras.layers.Layer):
40
60
  max_detections=100,
41
61
  **kwargs,
42
62
  ):
63
+ # Check whether current version of keras support bounding box utils
64
+ assert_bounding_box_support(self.__class__.__name__)
43
65
  super().__init__(**kwargs)
44
66
  self.bounding_box_format = bounding_box_format
45
67
  self.from_logits = from_logits
@@ -49,7 +71,10 @@ class NonMaxSuppression(keras.layers.Layer):
49
71
  self.built = True
50
72
 
51
73
  def call(
52
- self, box_prediction, class_prediction, images=None, image_shape=None
74
+ self,
75
+ box_prediction,
76
+ class_prediction,
77
+ images=None,
53
78
  ):
54
79
  """Accepts images and raw scores, returning bounding box predictions.
55
80
 
@@ -59,15 +84,24 @@ class NonMaxSuppression(keras.layers.Layer):
59
84
  class_prediction: Dense Tensor of shape [batch, boxes, num_classes].
60
85
  """
61
86
  target_format = "yxyx"
62
- if utils.is_relative(self.bounding_box_format):
63
- target_format = utils.as_relative(target_format)
87
+ height, width = None, None
88
+
89
+ if "rel" in self.bounding_box_format and images is None:
90
+ raise ValueError(
91
+ "`images` cannot be None when using relative "
92
+ "bounding box format."
93
+ )
64
94
 
65
- box_prediction = converters.convert_format(
95
+ if "rel" in self.bounding_box_format:
96
+ target_format = "rel_" + target_format
97
+ height, width, _ = ops.shape(images)
98
+
99
+ box_prediction = keras.utils.bounding_boxes.convert_format(
66
100
  box_prediction,
67
101
  source=self.bounding_box_format,
68
102
  target=target_format,
69
- images=images,
70
- image_shape=image_shape,
103
+ height=height,
104
+ width=width,
71
105
  )
72
106
  if self.from_logits:
73
107
  class_prediction = ops.sigmoid(class_prediction)
@@ -95,17 +129,17 @@ class NonMaxSuppression(keras.layers.Layer):
95
129
  class_prediction, ops.expand_dims(idx, axis=-1), axis=1
96
130
  )
97
131
 
98
- box_prediction = converters.convert_format(
132
+ box_prediction = keras.utils.bounding_boxes.convert_format(
99
133
  box_prediction,
100
134
  source=target_format,
101
135
  target=self.bounding_box_format,
102
- images=images,
103
- image_shape=image_shape,
136
+ height=height,
137
+ width=width,
104
138
  )
105
139
  bounding_boxes = {
106
140
  "boxes": box_prediction,
107
141
  "confidence": confidence_prediction,
108
- "classes": ops.argmax(class_prediction, axis=-1),
142
+ "labels": ops.argmax(class_prediction, axis=-1),
109
143
  "num_detections": valid_det,
110
144
  }
111
145
 
@@ -519,22 +553,40 @@ def mask_invalid_detections(bounding_boxes):
519
553
  returned value will also return `tf.RaggedTensor` representations.
520
554
  """
521
555
  # ensure we are complying with Keras bounding box format.
522
- info = validate_format.validate_format(bounding_boxes)
523
- if info["ragged"]:
556
+ if (
557
+ not isinstance(bounding_boxes, dict)
558
+ or "labels" not in bounding_boxes
559
+ or "boxes" not in bounding_boxes
560
+ ):
524
561
  raise ValueError(
525
- "`bounding_box.mask_invalid_detections()` requires inputs to be "
526
- "Dense tensors. Please call "
527
- "`bounding_box.to_dense(bounding_boxes)` before passing your boxes "
528
- "to `bounding_box.mask_invalid_detections()`."
562
+ "Expected `bounding_boxes` agurment to be a "
563
+ "dict with keys 'boxes' and 'labels'. Received: "
564
+ f"bounding_boxes={bounding_boxes}"
529
565
  )
566
+
530
567
  if "num_detections" not in bounding_boxes:
531
568
  raise ValueError(
532
569
  "`bounding_boxes` must have key 'num_detections' "
533
- "to be used with `bounding_box.mask_invalid_detections()`."
570
+ "to be used with `mask_invalid_detections()`."
534
571
  )
535
572
 
536
573
  boxes = bounding_boxes.get("boxes")
537
- classes = bounding_boxes.get("classes")
574
+ labels = bounding_boxes.get("labels")
575
+ if isinstance(boxes, list):
576
+ if not isinstance(labels, list):
577
+ raise ValueError(
578
+ "If `bounding_boxes['boxes']` is a list, then "
579
+ "`bounding_boxes['labels']` must also be a list."
580
+ f"Received: bounding_boxes['labels']={labels}"
581
+ )
582
+ if len(boxes) != len(labels):
583
+ raise ValueError(
584
+ "If `bounding_boxes['boxes']` and "
585
+ "`bounding_boxes['labels']` are both lists, "
586
+ "they must have the same length. Received: "
587
+ f"len(bounding_boxes['boxes'])={len(boxes)} and "
588
+ f"len(bounding_boxes['labels'])={len(labels)} and "
589
+ )
538
590
  confidence = bounding_boxes.get("confidence", None)
539
591
  num_detections = bounding_boxes.get("num_detections")
540
592
 
@@ -545,7 +597,7 @@ def mask_invalid_detections(bounding_boxes):
545
597
  )
546
598
  mask = mask < num_detections[:, None]
547
599
 
548
- classes = ops.where(mask, classes, -ops.ones_like(classes))
600
+ labels = ops.where(mask, labels, -ops.ones_like(labels))
549
601
 
550
602
  if confidence is not None:
551
603
  confidence = ops.where(mask, confidence, -ops.ones_like(confidence))
@@ -558,7 +610,7 @@ def mask_invalid_detections(bounding_boxes):
558
610
  result = bounding_boxes.copy()
559
611
 
560
612
  result["boxes"] = boxes
561
- result["classes"] = classes
613
+ result["labels"] = labels
562
614
  if confidence is not None:
563
615
  result["confidence"] = confidence
564
616
 
@@ -14,6 +14,7 @@ from keras_hub.src.utils.preset_utils import find_subclass
14
14
  from keras_hub.src.utils.preset_utils import get_preset_loader
15
15
  from keras_hub.src.utils.preset_utils import get_preset_saver
16
16
  from keras_hub.src.utils.python_utils import classproperty
17
+ from keras_hub.src.utils.tensor_utils import check_bounding_box_support
17
18
  from keras_hub.src.utils.tensor_utils import preprocessing_function
18
19
 
19
20
 
@@ -64,6 +65,12 @@ class ImageConverter(PreprocessingLayer):
64
65
  interpolation: String, the interpolation method.
65
66
  Supports `"bilinear"`, `"nearest"`, `"bicubic"`,
66
67
  `"lanczos3"`, `"lanczos5"`. Defaults to `"bilinear"`.
68
+ bounding_box_format: A string specifying the format of the bounding
69
+ boxes, one of `"xyxy"`, `"rel_xyxy"`, `"xywh"`, `"center_xywh"`,
70
+ `"yxyx"`, `"rel_yxyx"`. Specifies the format of the bounding boxes
71
+ which will be resized to `image_size` along with the image. To pass
72
+ bounding boxed to this layer, pass a dict with keys `"images"` and
73
+ `"bounding_boxes"` when calling the layer.
67
74
  data_format: String, either `"channels_last"` or `"channels_first"`.
68
75
  The ordering of the dimensions in the inputs. `"channels_last"`
69
76
  corresponds to inputs with shape `(batch, height, width, channels)`
@@ -100,6 +107,7 @@ class ImageConverter(PreprocessingLayer):
100
107
  crop_to_aspect_ratio=True,
101
108
  pad_to_aspect_ratio=False,
102
109
  interpolation="bilinear",
110
+ bounding_box_format="yxyx",
103
111
  data_format=None,
104
112
  **kwargs,
105
113
  ):
@@ -121,6 +129,9 @@ class ImageConverter(PreprocessingLayer):
121
129
 
122
130
  # Create the `Resizing` layer here even if it's not being used. That
123
131
  # allows us to make `image_size` a settable property.
132
+ resizing_kwargs = {}
133
+ if check_bounding_box_support():
134
+ resizing_kwargs["bounding_box_format"] = bounding_box_format
124
135
  self.resizing = keras.layers.Resizing(
125
136
  height=image_size[0] if image_size else None,
126
137
  width=image_size[1] if image_size else None,
@@ -130,12 +141,14 @@ class ImageConverter(PreprocessingLayer):
130
141
  data_format=data_format,
131
142
  dtype=self.dtype_policy,
132
143
  name="resizing",
144
+ **resizing_kwargs,
133
145
  )
134
146
  self.scale = scale
135
147
  self.offset = offset
136
148
  self.crop_to_aspect_ratio = crop_to_aspect_ratio
137
149
  self.pad_to_aspect_ratio = pad_to_aspect_ratio
138
150
  self.interpolation = interpolation
151
+ self.bounding_box_format = bounding_box_format
139
152
  self.data_format = standardize_data_format(data_format)
140
153
 
141
154
  @property
@@ -154,14 +167,22 @@ class ImageConverter(PreprocessingLayer):
154
167
 
155
168
  @preprocessing_function
156
169
  def call(self, inputs):
157
- x = inputs
158
170
  if self.image_size is not None:
159
- x = self.resizing(x)
171
+ inputs = self.resizing(inputs)
172
+ # Allow dictionary input for handling bounding boxes.
173
+ if isinstance(inputs, dict):
174
+ x = inputs["images"]
175
+ else:
176
+ x = inputs
160
177
  if self.scale is not None:
161
178
  x = x * self._expand_non_channel_dims(self.scale, x)
162
179
  if self.offset is not None:
163
180
  x = x + self._expand_non_channel_dims(self.offset, x)
164
- return x
181
+ if isinstance(inputs, dict):
182
+ inputs["images"] = x
183
+ else:
184
+ inputs = x
185
+ return inputs
165
186
 
166
187
  def _expand_non_channel_dims(self, value, inputs):
167
188
  unbatched = len(ops.shape(inputs)) == 3
@@ -192,6 +213,7 @@ class ImageConverter(PreprocessingLayer):
192
213
  "interpolation": self.interpolation,
193
214
  "crop_to_aspect_ratio": self.crop_to_aspect_ratio,
194
215
  "pad_to_aspect_ratio": self.pad_to_aspect_ratio,
216
+ "bounding_box_format": self.bounding_box_format,
195
217
  }
196
218
  )
197
219
  return config
@@ -4,20 +4,25 @@ from keras_hub.src.api_export import keras_hub_export
4
4
  from keras_hub.src.models.task import Task
5
5
 
6
6
 
7
- @keras_hub_export("keras_hub.models.ImageObjectDetector")
8
- class ImageObjectDetector(Task):
7
+ @keras_hub_export(
8
+ [
9
+ "keras_hub.models.ObjectDetector",
10
+ "keras_hub.models.ImageObjectDetector",
11
+ ]
12
+ )
13
+ class ObjectDetector(Task):
9
14
  """Base class for all image object detection tasks.
10
15
 
11
- The `ImageObjectDetector` tasks wrap a `keras_hub.models.Backbone` and
16
+ The `ObjectDetector` tasks wrap a `keras_hub.models.Backbone` and
12
17
  a `keras_hub.models.Preprocessor` to create a model that can be used for
13
- object detection. `ImageObjectDetector` tasks take an additional
18
+ object detection. `ObjectDetector` tasks take an additional
14
19
  `num_classes` argument, controlling the number of predicted output classes.
15
20
 
16
21
  To fine-tune with `fit()`, pass a dataset containing tuples of `(x, y)`
17
22
  labels where `x` is a string and `y` is dictionary with `boxes` and
18
23
  `classes`.
19
24
 
20
- All `ImageObjectDetector` tasks include a `from_preset()` constructor which
25
+ All `ObjectDetector` tasks include a `from_preset()` constructor which
21
26
  can be used to load a pre-trained config and weights.
22
27
  """
23
28
 
@@ -29,9 +34,9 @@ class ImageObjectDetector(Task):
29
34
  metrics=None,
30
35
  **kwargs,
31
36
  ):
32
- """Configures the `ImageObjectDetector` task for training.
37
+ """Configures the `ObjectDetector` task for training.
33
38
 
34
- The `ImageObjectDetector` task extends the default compilation signature
39
+ The `ObjectDetector` task extends the default compilation signature
35
40
  of `keras.Model.compile` with defaults for `optimizer`, `loss`, and
36
41
  `metrics`. To override these defaults, pass any value
37
42
  to these arguments during compilation.
@@ -5,20 +5,25 @@ from keras_hub.src.models.preprocessor import Preprocessor
5
5
  from keras_hub.src.utils.tensor_utils import preprocessing_function
6
6
 
7
7
 
8
- @keras_hub_export("keras_hub.models.ImageObjectDetectorPreprocessor")
9
- class ImageObjectDetectorPreprocessor(Preprocessor):
8
+ @keras_hub_export(
9
+ [
10
+ "keras_hub.models.ObjectDetectorPreprocessor",
11
+ "keras_hub.models.ImageObjectDetectorPreprocessor",
12
+ ]
13
+ )
14
+ class ObjectDetectorPreprocessor(Preprocessor):
10
15
  """Base class for object detector preprocessing layers.
11
16
 
12
- `ImageObjectDetectorPreprocessor` tasks wraps a
17
+ `ObjectDetectorPreprocessor` tasks wraps a
13
18
  `keras_hub.layers.Preprocessor` to create a preprocessing layer for
14
19
  object detection tasks. It is intended to be paired with a
15
20
  `keras_hub.models.ImageObjectDetector` task.
16
21
 
17
- All `ImageObjectDetectorPreprocessor` take three inputs, `x`, `y`, and
22
+ All `ObjectDetectorPreprocessor` take three inputs, `x`, `y`, and
18
23
  `sample_weight`. `x`, the first input, should always be included. It can
19
24
  be a image or batch of images. See examples below. `y` and `sample_weight`
20
- are optional inputs that will be passed through unaltered. Usually, `y` will
21
- be the a dict of `{"boxes": Tensor(batch_size, num_boxes, 4),
25
+ are optional inputs that will be passed through unaltered. Usually, `y`
26
+ will be the a dict of `{"boxes": Tensor(batch_size, num_boxes, 4),
22
27
  "classes": (batch_size, num_boxes)}.
23
28
 
24
29
  The layer will returns either `x`, an `(x, y)` tuple if labels were
@@ -26,18 +31,18 @@ class ImageObjectDetectorPreprocessor(Preprocessor):
26
31
  were provided. `x` will be the input images after all model preprocessing
27
32
  has been applied.
28
33
 
29
- All `ImageObjectDetectorPreprocessor` tasks include a `from_preset()`
30
- constructor which can be used to load a pre-trained config and vocabularies.
31
- You can call the `from_preset()` constructor directly on this base class, in
32
- which case the correct class for your model will be automatically
33
- instantiated.
34
+ All `ObjectDetectorPreprocessor` tasks include a `from_preset()`
35
+ constructor which can be used to load a pre-trained config and
36
+ vocabularies. You can call the `from_preset()` constructor directly on
37
+ this base class, in which case the correct class for your model will be
38
+ automatically instantiated.
34
39
 
35
40
  Args:
36
41
  image_converter: Preprocessing pipeline for images.
37
42
 
38
43
  Examples.
39
44
  ```python
40
- preprocessor = keras_hub.models.ImageObjectDetectorPreprocessor.from_preset(
45
+ preprocessor = keras_hub.models.ObjectDetectorPreprocessor.from_preset(
41
46
  "retinanet_resnet50",
42
47
  )
43
48
  """
@@ -52,6 +57,17 @@ class ImageObjectDetectorPreprocessor(Preprocessor):
52
57
 
53
58
  @preprocessing_function
54
59
  def call(self, x, y=None, sample_weight=None):
55
- if self.image_converter:
60
+ if y is None:
56
61
  x = self.image_converter(x)
62
+ else:
63
+ # Pass bounding boxes through image converter in the dictionary
64
+ # with keys format standardized by core Keras.
65
+ output = self.image_converter(
66
+ {
67
+ "images": x,
68
+ "bounding_boxes": y,
69
+ }
70
+ )
71
+ x = output["images"]
72
+ y = output["bounding_boxes"]
57
73
  return keras.utils.pack_x_y_sample_weight(x, y, sample_weight)
@@ -1,7 +1,6 @@
1
1
  from keras_hub.src.api_export import keras_hub_export
2
2
  from keras_hub.src.layers.preprocessing.image_converter import ImageConverter
3
3
  from keras_hub.src.models.retinanet.retinanet_backbone import RetinaNetBackbone
4
- from keras_hub.src.utils.tensor_utils import preprocessing_function
5
4
 
6
5
 
7
6
  @keras_hub_export("keras_hub.layers.RetinaNetImageConverter")
@@ -10,44 +9,13 @@ class RetinaNetImageConverter(ImageConverter):
10
9
 
11
10
  def __init__(
12
11
  self,
13
- image_size=None,
14
- scale=None,
15
- offset=None,
16
- norm_mean=[0.485, 0.456, 0.406],
17
- norm_std=[0.229, 0.224, 0.225],
12
+ *args,
18
13
  **kwargs,
19
14
  ):
20
- super().__init__(**kwargs)
21
- self.image_size = image_size
22
- self.scale = scale
23
- self.offset = offset
24
- self.norm_mean = norm_mean
25
- self.norm_std = norm_std
26
- self.built = True
27
-
28
- @preprocessing_function
29
- def call(self, inputs):
30
- # TODO: https://github.com/keras-team/keras-hub/issues/1965
31
- x = inputs
32
- # Rescaling Image
33
- if self.scale is not None:
34
- x = x * self._expand_non_channel_dims(self.scale, x)
35
- if self.offset is not None:
36
- x = x + self._expand_non_channel_dims(self.offset, x)
37
- # By default normalize using imagenet mean and std
38
- if self.norm_mean:
39
- x = x - self._expand_non_channel_dims(self.norm_mean, x)
40
- if self.norm_std:
41
- x = x / self._expand_non_channel_dims(self.norm_std, x)
42
-
43
- return x
44
-
45
- def get_config(self):
46
- config = super().get_config()
47
- config.update(
48
- {
49
- "norm_mean": self.norm_mean,
50
- "norm_std": self.norm_std,
51
- }
52
- )
53
- return config
15
+ # TODO: update presets and remove these old config options. They were
16
+ # never needed.
17
+ if "norm_mean" in kwargs:
18
+ kwargs["offset"] = [-x for x in kwargs.pop("norm_mean")]
19
+ if "norm_std" in kwargs:
20
+ kwargs["scale"] = [1.0 / x for x in kwargs.pop("norm_std")]
21
+ super().__init__(*args, **kwargs)