Repository: deepjavalibrary/djl
Pull request #3425 (2024-08-17 21:41:23)

[api] Adds center fit image operation for Yolo
- **[pytorch] Adds pad operator**
- **[api] Adds center fit image operation for Yolo**

## Description ##

Brief description of what this PR is about

- If this change is a backward incompatible change, why must this change be made?
- Interesting edge cases to note here


codecov-commenter: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) Report
Attention: Patch coverage is `39.39394%` with `40 lines` in your changes missing coverage. Please review.
> Project coverage is 58.28%. Comparing base [(`c4baffb`)](https://app.codecov.io/gh/deepjavalibrary/djl/commit/c4baffbcf1ff9974f3e19ac1b68feff8d019b072?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) to head [(`b87cc7f`)](https://app.codecov.io/gh/deepjavalibrary/djl/commit/b87cc7f79e843f89f848a30bf69ba5e0456418da?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary).
> Report is 18 commits behind head on master.

| [Files](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) | Patch % | Lines |
|---|---|---|
| [...n/java/ai/djl/modality/cv/transform/CenterFit.java](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&filepath=api%2Fsrc%2Fmain%2Fjava%2Fai%2Fdjl%2Fmodality%2Fcv%2Ftransform%2FCenterFit.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zZm9ybS9DZW50ZXJGaXQuamF2YQ==) | 21.05% | [15 Missing :warning: ](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) |
| [...i/djl/modality/cv/translator/YoloV5Translator.java](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&filepath=api%2Fsrc%2Fmain%2Fjava%2Fai%2Fdjl%2Fmodality%2Fcv%2Ftranslator%2FYoloV5Translator.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1Y1VHJhbnNsYXRvci5qYXZh) | 47.36% | [8 Missing and 2 partials :warning: ](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) |
| [...y/cv/translator/SingleShotDetectionTranslator.java](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&filepath=api%2Fsrc%2Fmain%2Fjava%2Fai%2Fdjl%2Fmodality%2Fcv%2Ftranslator%2FSingleShotDetectionTranslator.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvU2luZ2xlU2hvdERldGVjdGlvblRyYW5zbGF0b3IuamF2YQ==) | 0.00% | [1 Missing and 4 partials :warning: ](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) |
| [.../ai/djl/modality/cv/translator/YoloTranslator.java](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&filepath=api%2Fsrc%2Fmain%2Fjava%2Fai%2Fdjl%2Fmodality%2Fcv%2Ftranslator%2FYoloTranslator.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1RyYW5zbGF0b3IuamF2YQ==) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) |
| [...jl/modality/cv/translator/BaseImageTranslator.java](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&filepath=api%2Fsrc%2Fmain%2Fjava%2Fai%2Fdjl%2Fmodality%2Fcv%2Ftranslator%2FBaseImageTranslator.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvQmFzZUltYWdlVHJhbnNsYXRvci5qYXZh) | 71.42% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) |
| [...i/src/main/java/ai/djl/ndarray/NDArrayAdapter.java](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&filepath=api%2Fsrc%2Fmain%2Fjava%2Fai%2Fdjl%2Fndarray%2FNDArrayAdapter.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL25kYXJyYXkvTkRBcnJheUFkYXB0ZXIuamF2YQ==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) |
| [...e/src/main/java/ai/djl/mxnet/engine/MxNDArray.java](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&filepath=engines%2Fmxnet%2Fmxnet-engine%2Fsrc%2Fmain%2Fjava%2Fai%2Fdjl%2Fmxnet%2Fengine%2FMxNDArray.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-ZW5naW5lcy9teG5ldC9teG5ldC1lbmdpbmUvc3JjL21haW4vamF2YS9haS9kamwvbXhuZXQvZW5naW5lL014TkRBcnJheS5qYXZh) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) |
| [.../main/java/ai/djl/tensorflow/engine/TfNDArray.java](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&filepath=engines%2Ftensorflow%2Ftensorflow-engine%2Fsrc%2Fmain%2Fjava%2Fai%2Fdjl%2Ftensorflow%2Fengine%2FTfNDArray.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-ZW5naW5lcy90ZW5zb3JmbG93L3RlbnNvcmZsb3ctZW5naW5lL3NyYy9tYWluL2phdmEvYWkvZGpsL3RlbnNvcmZsb3cvZW5naW5lL1RmTkRBcnJheS5qYXZh) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) |
| [...rs/src/main/java/ai/djl/engine/rust/RsNDArray.java](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&filepath=extensions%2Ftokenizers%2Fsrc%2Fmain%2Fjava%2Fai%2Fdjl%2Fengine%2Frust%2FRsNDArray.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-ZXh0ZW5zaW9ucy90b2tlbml6ZXJzL3NyYy9tYWluL2phdmEvYWkvZGpsL2VuZ2luZS9ydXN0L1JzTkRBcnJheS5qYXZh) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) |

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #3425      +/-   ##
============================================
+ Coverage     58.25%   58.28%   +0.03%     
- Complexity     5737     5775      +38     
============================================
  Files           668      669       +1     
  Lines         32509    32654     +145     
  Branches       3439     3472      +33     
============================================
+ Hits          18937    19033      +96     
- Misses        12125    12163      +38     
- Partials       1447     1458      +11     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/deepjavalibrary/djl/pull/3425?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary).


Patch:
diff --git a/api/src/main/java/ai/djl/modality/cv/transform/CenterFit.java b/api/src/main/java/ai/djl/modality/cv/transform/CenterFit.java
new file mode 100644
index 000000000..17b910ccf
--- /dev/null
+++ b/api/src/main/java/ai/djl/modality/cv/transform/CenterFit.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.modality.cv.transform;
+
+import ai.djl.modality.cv.util.NDImageUtils;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.types.Shape;
+import ai.djl.translate.Transform;
+
+/** A {@link Transform} that fit the size of an image. */
+public class CenterFit implements Transform {
+
+    private int width;
+    private int height;
+
+    /**
+     * Creates a {@code CenterFit} {@link Transform} that fit to the given width and height with
+     * given interpolation.
+     *
+     * @param width the desired width
+     * @param height the desired height
+     */
+    public CenterFit(int width, int height) {
+        this.width = width;
+        this.height = height;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NDArray transform(NDArray array) {
+        Shape shape = array.getShape();
+        int w = (int) shape.get(1);
+        int h = (int) shape.get(0);
+        if (w > width || h > height) {
+            array = NDImageUtils.centerCrop(array, Math.min(w, width), Math.min(h, height));
+        }
+        int padW = width - w;
+        int padH = height - h;
+        if (padW > 0 || padH > 0) {
+            padW = Math.max(0, padW);
+            padH = Math.max(0, padH);
+            int padW1 = padW / 2;
+            int padH1 = padH / 2;
+            Shape padding = new Shape(0, 0, padW1, padW - padW1, padH1, padH - padH1);
+            array = array.pad(padding, 0);
+        }
+        return array;
+    }
+}
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslator.java
index a0352ff8b..3a66df732 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslator.java
@@ -15,6 +15,7 @@ package ai.djl.modality.cv.translator;
 import ai.djl.Model;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.transform.CenterCrop;
+import ai.djl.modality.cv.transform.CenterFit;
 import ai.djl.modality.cv.transform.Normalize;
 import ai.djl.modality.cv.transform.Resize;
 import ai.djl.modality.cv.transform.ToTensor;
@@ -50,6 +51,8 @@ public abstract class BaseImageTranslator<T> implements Translator<Image, T> {
 
     private Image.Flag flag;
     private Batchifier batchifier;
+    protected int width;
+    protected int height;
 
     /**
      * Constructs an ImageTranslator with the provided builder.
@@ -60,6 +63,8 @@ public abstract class BaseImageTranslator<T> implements Translator<Image, T> {
         flag = builder.flag;
         pipeline = builder.pipeline;
         batchifier = builder.batchifier;
+        width = builder.width;
+        height = builder.height;
     }
 
     /** {@inheritDoc} */
@@ -72,6 +77,8 @@ public abstract class BaseImageTranslator<T> implements Translator<Image, T> {
     @Override
     public NDList processInput(TranslatorContext ctx, Image input) {
         NDArray array = input.toNDArray(ctx.getNDManager(), flag);
+        ctx.setAttachment("width", input.getWidth());
+        ctx.setAttachment("height", input.getHeight());
         return pipeline.transform(new NDList(array));
     }
 
@@ -171,6 +178,10 @@ public abstract class BaseImageTranslator<T> implements Translator<Image, T> {
             if (ArgumentsUtil.booleanValue(arguments, "centerCrop", false)) {
                 addTransform(new CenterCrop(width, height));
             }
+            String centerFit = ArgumentsUtil.stringValue(arguments, "centerFit", "false");
+            if ("true".equals(centerFit)) {
+                addTransform(new CenterFit(width, height));
+            }
             if (ArgumentsUtil.booleanValue(arguments, "toTensor", true)) {
                 addTransform(new ToTensor());
             }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/ObjectDetectionTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/ObjectDetectionTranslator.java
index 64e2feadb..f16d1b1c6 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/ObjectDetectionTranslator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/ObjectDetectionTranslator.java
@@ -29,9 +29,8 @@ public abstract class ObjectDetectionTranslator extends BaseImageTranslator<Dete
     protected float threshold;
     private SynsetLoader synsetLoader;
     protected List<String> classes;
-    protected double imageWidth;
-    protected double imageHeight;
     protected boolean applyRatio;
+    protected boolean removePadding;
 
     /**
      * Creates the {@link ObjectDetectionTranslator} from the given builder.
@@ -42,9 +41,8 @@ public abstract class ObjectDetectionTranslator extends BaseImageTranslator<Dete
         super(builder);
         this.threshold = builder.threshold;
         this.synsetLoader = builder.synsetLoader;
-        this.imageWidth = builder.imageWidth;
-        this.imageHeight = builder.imageHeight;
         this.applyRatio = builder.applyRatio;
+        this.removePadding = builder.removePadding;
     }
 
     /** {@inheritDoc} */
@@ -61,9 +59,8 @@ public abstract class ObjectDetectionTranslator extends BaseImageTranslator<Dete
             extends ClassificationBuilder<T> {
 
         protected float threshold = 0.2f;
-        protected double imageWidth;
-        protected double imageHeight;
         protected boolean applyRatio;
+        protected boolean removePadding;
 
         /**
          * Sets the threshold for prediction accuracy.
@@ -78,19 +75,6 @@ public abstract class ObjectDetectionTranslator extends BaseImageTranslator<Dete
             return self();
         }
 
-        /**
-         * Sets the optional rescale size.
-         *
-         * @param imageWidth the width to rescale images to
-         * @param imageHeight the height to rescale images to
-         * @return this builder
-         */
-        public T optRescaleSize(double imageWidth, double imageHeight) {
-            this.imageWidth = imageWidth;
-            this.imageHeight = imageHeight;
-            return self();
-        }
-
         /**
          * Determine Whether to divide output object width/height on the inference result. Default
          * false.
@@ -108,37 +92,17 @@ public abstract class ObjectDetectionTranslator extends BaseImageTranslator<Dete
             return self();
         }
 
-        /**
-         * Get resized image width.
-         *
-         * @return image width
-         */
-        public double getImageWidth() {
-            return imageWidth;
-        }
-
-        /**
-         * Get resized image height.
-         *
-         * @return image height
-         */
-        public double getImageHeight() {
-            return imageHeight;
-        }
-
         /** {@inheritDoc} */
         @Override
         protected void configPostProcess(Map<String, ?> arguments) {
             super.configPostProcess(arguments);
-            if (ArgumentsUtil.booleanValue(arguments, "rescale")) {
-                optRescaleSize(width, height);
-            }
             if (ArgumentsUtil.booleanValue(arguments, "optApplyRatio")
                     || ArgumentsUtil.booleanValue(arguments, "applyRatio")) {
                 optApplyRatio(true);
-                optRescaleSize(width, height);
             }
             threshold = ArgumentsUtil.floatValue(arguments, "threshold", 0.2f);
+            String centerFit = ArgumentsUtil.stringValue(arguments, "centerFit", "false");
+            removePadding = "true".equals(centerFit);
         }
     }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslator.java
index 40b24f26e..a04780f3f 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslator.java
@@ -59,19 +59,14 @@ public class SingleShotDetectionTranslator extends ObjectDetectionTranslator {
                 }
                 String className = classes.get(classId);
                 float[] box = boundingBoxes.get(i).toFloatArray();
-                // rescale box coordinates by imageWidth and imageHeight
-                double x = imageWidth > 0 ? box[0] / imageWidth : box[0];
-                double y = imageHeight > 0 ? box[1] / imageHeight : box[1];
-                double w = imageWidth > 0 ? box[2] / imageWidth - x : box[2] - x;
-                double h = imageHeight > 0 ? box[3] / imageHeight - y : box[3] - y;
+                // rescale box coordinates by width and height
+                double x = width > 0 ? box[0] / width : box[0];
+                double y = height > 0 ? box[1] / height : box[1];
+                double w = width > 0 ? box[2] / width - x : box[2] - x;
+                double h = height > 0 ? box[3] / height - y : box[3] - y;
                 Rectangle rect;
                 if (applyRatio) {
-                    rect =
-                            new Rectangle(
-                                    x / imageWidth,
-                                    y / imageHeight,
-                                    w / imageWidth,
-                                    h / imageHeight);
+                    rect = new Rectangle(x / width, y / height, w / width, h / height);
                 } else {
                     rect = new Rectangle(x, y, w, h);
                 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/YoloPoseTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/YoloPoseTranslator.java
index b59da7020..45b1da77b 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/YoloPoseTranslator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/YoloPoseTranslator.java
@@ -29,8 +29,6 @@ public class YoloPoseTranslator extends BaseImageTranslator<Joints[]> {
 
     private static final int MAX_DETECTION = 300;
 
-    private int width;
-    private int height;
     private float threshold;
     private float nmsThreshold;
 
@@ -41,8 +39,6 @@ public class YoloPoseTranslator extends BaseImageTranslator<Joints[]> {
      */
     public YoloPoseTranslator(Builder builder) {
         super(builder);
-        this.width = builder.width;
-        this.height = builder.height;
         this.threshold = builder.threshold;
         this.nmsThreshold = builder.nmsThreshold;
     }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/YoloSegmentationTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/YoloSegmentationTranslator.java
index 1d0ad777d..ff0f4b4e0 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/YoloSegmentationTranslator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/YoloSegmentationTranslator.java
@@ -33,8 +33,6 @@ public class YoloSegmentationTranslator extends YoloV5Translator {
 
     private float threshold;
     private float nmsThreshold;
-    private int width;
-    private int height;
 
     /**
      * Creates the instance segmentation translator from the given builder.
@@ -45,8 +43,6 @@ public class YoloSegmentationTranslator extends YoloV5Translator {
         super(builder);
         this.threshold = builder.threshold;
         this.nmsThreshold = builder.nmsThreshold;
-        this.width = builder.width;
-        this.height = builder.height;
     }
 
     /** {@inheritDoc} */
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/YoloTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/YoloTranslator.java
index 6cb411158..6d54336c9 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/YoloTranslator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/YoloTranslator.java
@@ -44,10 +44,10 @@ public class YoloTranslator extends ObjectDetectionTranslator {
         NDArray boundingBoxes = list.get(2);
         int detected = Math.toIntExact(probs.length);
 
-        NDArray xMin = boundingBoxes.get(":, 0").clip(0, imageWidth).div(imageWidth);
-        NDArray yMin = boundingBoxes.get(":, 1").clip(0, imageHeight).div(imageHeight);
-        NDArray xMax = boundingBoxes.get(":, 2").clip(0, imageWidth).div(imageWidth);
-        NDArray yMax = boundingBoxes.get(":, 3").clip(0, imageHeight).div(imageHeight);
+        NDArray xMin = boundingBoxes.get(":, 0").clip(0, width).div(width);
+        NDArray yMin = boundingBoxes.get(":, 1").clip(0, height).div(height);
+        NDArray xMax = boundingBoxes.get(":, 2").clip(0, width).div(width);
+        NDArray yMax = boundingBoxes.get(":, 3").clip(0, height).div(height);
 
         float[] boxX = xMin.toFloatArray();
         float[] boxY = yMin.toFloatArray();
@@ -67,10 +67,10 @@ public class YoloTranslator extends ObjectDetectionTranslator {
             if (applyRatio) {
                 rect =
                         new Rectangle(
-                                boxX[i] / imageWidth,
-                                boxY[i] / imageHeight,
-                                boxWidth[i] / imageWidth,
-                                boxHeight[i] / imageHeight);
+                                boxX[i] / width,
+                                boxY[i] / height,
+                                boxWidth[i] / width,
+                                boxHeight[i] / height);
             } else {
                 rect = new Rectangle(boxX[i], boxY[i], boxWidth[i], boxHeight[i]);
             }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/YoloV5Translator.java b/api/src/main/java/ai/djl/modality/cv/translator/YoloV5Translator.java
index 67682d7c8..2cb80418d 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/YoloV5Translator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/YoloV5Translator.java
@@ -27,7 +27,7 @@ import java.util.Map;
 
 /**
  * A translator for YoloV5 models. This was tested with ONNX exported Yolo models. For details check
- * here: https://github.com/ultralytics/yolov5
+ * <a href="https://github.com/ultralytics/yolov5">here</a>
  */
 public class YoloV5Translator extends ObjectDetectionTranslator {
 
@@ -68,7 +68,11 @@ public class YoloV5Translator extends ObjectDetectionTranslator {
     }
 
     protected DetectedObjects nms(
-            List<Rectangle> boxes, List<Integer> classIds, List<Float> scores) {
+            int imageWidth,
+            int imageHeight,
+            List<Rectangle> boxes,
+            List<Integer> classIds,
+            List<Float> scores) {
         List<String> retClasses = new ArrayList<>();
         List<Double> retProbs = new ArrayList<>();
         List<BoundingBox> retBB = new ArrayList<>();
@@ -94,22 +98,30 @@ public class YoloV5Translator extends ObjectDetectionTranslator {
                 retClasses.add(classes.get(id));
                 retProbs.add(scores.get(pos).doubleValue());
                 Rectangle rect = boxes.get(pos);
-                if (applyRatio) {
-                    retBB.add(
+                if (removePadding) {
+                    int padW = (width - imageWidth) / 2;
+                    int padH = (height - imageHeight) / 2;
+                    rect =
                             new Rectangle(
-                                    rect.getX() / imageWidth,
-                                    rect.getY() / imageHeight,
+                                    (rect.getX() - padW) / imageWidth,
+                                    (rect.getY() - padH) / imageHeight,
                                     rect.getWidth() / imageWidth,
-                                    rect.getHeight() / imageHeight));
-                } else {
-                    retBB.add(rect);
+                                    rect.getHeight() / imageHeight);
+                } else if (applyRatio) {
+                    rect =
+                            new Rectangle(
+                                    rect.getX() / width,
+                                    rect.getY() / height,
+                                    rect.getWidth() / width,
+                                    rect.getHeight() / height);
                 }
+                retBB.add(rect);
             }
         }
         return new DetectedObjects(retClasses, retProbs, retBB);
     }
 
-    protected DetectedObjects processFromBoxOutput(NDList list) {
+    protected DetectedObjects processFromBoxOutput(int imageWidth, int imageHeight, NDList list) {
         float[] flattened = list.get(0).toFloatArray();
         int sizeClasses = classes.size();
         int stride = 5 + sizeClasses;
@@ -142,7 +154,7 @@ public class YoloV5Translator extends ObjectDetectionTranslator {
                 classIds.add(maxIndex);
             }
         }
-        return nms(boxes, classIds, scores);
+        return nms(imageWidth, imageHeight, boxes, classIds, scores);
     }
 
     private DetectedObjects processFromDetectOutput() {
@@ -153,6 +165,8 @@ public class YoloV5Translator extends ObjectDetectionTranslator {
     /** {@inheritDoc} */
     @Override
     public DetectedObjects processOutput(TranslatorContext ctx, NDList list) {
+        int imageWidth = (Integer) ctx.getAttachment("width");
+        int imageHeight = (Integer) ctx.getAttachment("height");
         switch (yoloOutputLayerType) {
             case DETECT:
                 return processFromDetectOutput();
@@ -160,11 +174,11 @@ public class YoloV5Translator extends ObjectDetectionTranslator {
                 if (list.get(0).getShape().dimension() > 2) {
                     return processFromDetectOutput();
                 } else {
-                    return processFromBoxOutput(list);
+                    return processFromBoxOutput(imageWidth, imageHeight, list);
                 }
             case BOX:
             default:
-                return processFromBoxOutput(list);
+                return processFromBoxOutput(imageWidth, imageHeight, list);
         }
     }
 
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/YoloV8Translator.java b/api/src/main/java/ai/djl/modality/cv/translator/YoloV8Translator.java
index 2101d9bbb..7c4270ebb 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/YoloV8Translator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/YoloV8Translator.java
@@ -25,7 +25,7 @@ import java.util.Map;
 
 /**
  * A translator for YoloV8 models. This was tested with ONNX exported Yolo models. For details check
- * here: https://github.com/ultralytics/ultralytics
+ * <a href="https://github.com/ultralytics/ultralytics">here</a>
  */
 public class YoloV8Translator extends YoloV5Translator {
 
@@ -66,7 +66,7 @@ public class YoloV8Translator extends YoloV5Translator {
 
     /** {@inheritDoc} */
     @Override
-    protected DetectedObjects processFromBoxOutput(NDList list) {
+    protected DetectedObjects processFromBoxOutput(int imageWidth, int imageHeight, NDList list) {
         NDArray rawResult = list.get(0);
         NDArray reshapedResult = rawResult.transpose();
         Shape shape = reshapedResult.getShape();
@@ -110,7 +110,7 @@ public class YoloV8Translator extends YoloV5Translator {
             }
         }
 
-        return nms(boxes, classIds, scores);
+        return nms(imageWidth, imageHeight, boxes, classIds, scores);
     }
 
     /** The builder for {@link YoloV8Translator}. */
diff --git a/api/src/main/java/ai/djl/ndarray/NDArray.java b/api/src/main/java/ai/djl/ndarray/NDArray.java
index d9227a3c3..3d93c9878 100644
--- a/api/src/main/java/ai/djl/ndarray/NDArray.java
+++ b/api/src/main/java/ai/djl/ndarray/NDArray.java
@@ -3518,6 +3518,24 @@ public interface NDArray extends NDResource, BytesSupplier {
         return ifft2(sizes, new long[] {-2, -1});
     }
 
+    /**
+     * Pads this {@code NDArray} with the given {@link Shape}.
+     *
+     * <p>Examples
+     *
+     * <pre>
+     * NDArray array = manager.zeros(3, 3, 4, 2);
+     * array.pad(new Shape(1, 1), 0); # pad last dim by 1 on each side
+     * array.getShape() => (3, 3, 4, 4)
+     * </pre>
+     *
+     * @param padding the padding shape, must be even
+     * @return a padded {@code NDArray}
+     * @throws IllegalArgumentException thrown if the given {@code padding} does not match the size
+     *     of the current shape
+     */
+    NDArray pad(Shape padding, double value);
+
     /**
      * Reshapes this {@code NDArray} to the given {@link Shape}.
      *
diff --git a/api/src/main/java/ai/djl/ndarray/NDArrayAdapter.java b/api/src/main/java/ai/djl/ndarray/NDArrayAdapter.java
index 27c362c88..04ea056b4 100644
--- a/api/src/main/java/ai/djl/ndarray/NDArrayAdapter.java
+++ b/api/src/main/java/ai/djl/ndarray/NDArrayAdapter.java
@@ -936,6 +936,12 @@ public abstract class NDArrayAdapter implements NDArray {
         throw new UnsupportedOperationException("Not implemented yet.");
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public NDArray pad(Shape padding, double value) {
+        throw new UnsupportedOperationException("Not implemented yet.");
+    }
+
     /** {@inheritDoc} */
     @Override
     public NDArray ifft2(long[] sizes, long[] axes) {
diff --git a/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArray.java b/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArray.java
index f6d2c0aa0..85f940d54 100644
--- a/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArray.java
+++ b/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArray.java
@@ -1194,6 +1194,12 @@ public class MxNDArray extends NativeResource<Pointer> implements LazyNDArray {
         throw new UnsupportedOperationException("Not implemented yet.");
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public NDArray pad(Shape padding, double value) {
+        throw new UnsupportedOperationException("Not implemented yet.");
+    }
+
     /** {@inheritDoc} */
     @Override
     public NDArray reshape(Shape shape) {
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArray.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArray.java
index 1edd61226..04c15f79b 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArray.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArray.java
@@ -1144,6 +1144,12 @@ public class PtNDArray extends NativeResource<Long> implements NDArray {
         return JniUtils.ifft2(this, sizes, axes);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public NDArray pad(Shape padding, double value) {
+        return JniUtils.pad(this, padding.getShape(), value);
+    }
+
     /** {@inheritDoc} */
     @Override
     public PtNDArray reshape(Shape shape) {
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java
index b80c096cb..8f1ac296c 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java
@@ -616,6 +616,12 @@ public final class JniUtils {
                 ndArray.getManager(), PyTorchLibrary.LIB.tensorClone(ndArray.getHandle()));
     }
 
+    public static PtNDArray pad(PtNDArray ndArray, long[] shape, double value) {
+        return new PtNDArray(
+                ndArray.getManager(),
+                PyTorchLibrary.LIB.torchPad(ndArray.getHandle(), shape, value));
+    }
+
     public static PtNDArray reshape(PtNDArray ndArray, long[] shape) {
         return new PtNDArray(
                 ndArray.getManager(), PyTorchLibrary.LIB.torchReshape(ndArray.getHandle(), shape));
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java
index 5c1fd290f..dfe7e0d88 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java
@@ -165,6 +165,8 @@ final class PyTorchLibrary {
 
     native long torchLogicalNot(long handle);
 
+    native long torchPad(long handle, long[] shape, double value);
+
     native long torchReshape(long handle, long[] shape);
 
     native long torchSoftmax(long handle, long dim, int dType);
diff --git a/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_nn_functional.cc b/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_nn_functional.cc
index 3f1bcfd58..033cc5576 100644
--- a/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_nn_functional.cc
+++ b/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_nn_functional.cc
@@ -19,6 +19,16 @@
 
 // The file is the implementation for PyTorch neural network functional ops
 
+JNIEXPORT jlong JNICALL Java_ai_djl_pytorch_jni_PyTorchLibrary_torchPad(
+    JNIEnv* env, jobject jthis, jlong jhandle, jlongArray jshape, jdouble jvalue) {
+  API_BEGIN()
+  const auto* tensor_ptr = reinterpret_cast<torch::Tensor*>(jhandle);
+  const auto shape_vec = djl::utils::jni::GetVecFromJLongArray(env, jshape);
+  const auto* result_ptr = new torch::Tensor(pad(*tensor_ptr, c10::ArrayRef(shape_vec), "constant", jvalue));
+  return reinterpret_cast<uintptr_t>(result_ptr);
+  API_END_RETURN()
+}
+
 JNIEXPORT jlong JNICALL Java_ai_djl_pytorch_jni_PyTorchLibrary_torchSoftmax(
     JNIEnv* env, jobject jthis, jlong jhandle, jlong jdim, jint jdtype) {
   API_BEGIN()
diff --git a/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/TfNDArray.java b/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/TfNDArray.java
index 88df43498..4845ffd09 100644
--- a/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/TfNDArray.java
+++ b/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/TfNDArray.java
@@ -1215,6 +1215,12 @@ public class TfNDArray extends NativeResource<TFE_TensorHandle> implements NDArr
         throw new UnsupportedOperationException("Not implemented yet.");
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public NDArray pad(Shape padding, double value) {
+        throw new UnsupportedOperationException("Not implemented yet.");
+    }
+
     /** {@inheritDoc} */
     @Override
     public NDArray ifft2(long[] sizes, long[] axes) {
diff --git a/extensions/tokenizers/src/main/java/ai/djl/engine/rust/RsNDArray.java b/extensions/tokenizers/src/main/java/ai/djl/engine/rust/RsNDArray.java
index 97e1148bf..8e6e4f3c7 100644
--- a/extensions/tokenizers/src/main/java/ai/djl/engine/rust/RsNDArray.java
+++ b/extensions/tokenizers/src/main/java/ai/djl/engine/rust/RsNDArray.java
@@ -171,6 +171,12 @@ public class RsNDArray extends NativeResource<Long> implements NDArray {
             long newHandle = RustLibrary.toBoolean(getHandle());
             return toArray(newHandle, dataType, false, true);
         }
+        if (this.dataType == DataType.INT64
+                && dataType == DataType.FLOAT16
+                && getDevice().isGpu()) {
+            // TODO:
+            throw new UnsupportedOperationException("FP16 to I64 is not supported on GPU.");
+        }
         int dType = manager.toRustDataType(dataType);
         long newHandle = RustLibrary.toDataType(getHandle(), dType);
         return toArray(newHandle, dataType, false, true);
@@ -1125,6 +1131,12 @@ public class RsNDArray extends NativeResource<Long> implements NDArray {
         throw new UnsupportedOperationException("Not implemented");
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public NDArray pad(Shape padding, double value) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
     /** {@inheritDoc} */
     @Override
     public NDArray ifft2(long[] sizes, long[] axes) {


Test patch:
diff --git a/extensions/tokenizers/src/test/java/ai/djl/engine/rust/NDArrayTests.java b/extensions/tokenizers/src/test/java/ai/djl/engine/rust/NDArrayTests.java
index 5ef89ee95..2bc0f79b0 100644
--- a/extensions/tokenizers/src/test/java/ai/djl/engine/rust/NDArrayTests.java
+++ b/extensions/tokenizers/src/test/java/ai/djl/engine/rust/NDArrayTests.java
@@ -20,7 +20,6 @@ import ai.djl.ndarray.types.DataType;
 import ai.djl.ndarray.types.Shape;
 import ai.djl.nn.Activation;
 import ai.djl.testing.Assertions;
-import ai.djl.testing.TestRequirements;
 
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -119,14 +118,17 @@ public class NDArrayTests {
 
     @Test
     public void testI64toF16() {
-        TestRequirements.notGpu("Rust");
         try (NDManager manager = NDManager.newBaseManager("Rust")) {
             NDArray array = manager.create(2);
             Assert.assertEquals(array.getDataType(), DataType.INT32);
             NDArray int64 = array.toType(DataType.INT64, false);
-            NDArray f16 = int64.toType(DataType.FLOAT16, false);
-            NDArray bool = f16.toType(DataType.BOOLEAN, false);
-            Assert.assertTrue(bool.getBoolean());
+            try {
+                NDArray f16 = int64.toType(DataType.FLOAT16, false);
+                NDArray bool = f16.toType(DataType.BOOLEAN, false);
+                Assert.assertTrue(bool.getBoolean());
+            } catch (UnsupportedOperationException e) {
+                // ignore on GPU
+            }
         }
     }
 
diff --git a/integration/src/main/java/ai/djl/integration/tests/ndarray/NDArrayShapesManipulationOpTest.java b/integration/src/main/java/ai/djl/integration/tests/ndarray/NDArrayShapesManipulationOpTest.java
index 87cfd4435..44858406a 100644
--- a/integration/src/main/java/ai/djl/integration/tests/ndarray/NDArrayShapesManipulationOpTest.java
+++ b/integration/src/main/java/ai/djl/integration/tests/ndarray/NDArrayShapesManipulationOpTest.java
@@ -86,6 +86,22 @@ public class NDArrayShapesManipulationOpTest {
         }
     }
 
+    @Test
+    public void testPad() {
+        try (NDManager manager = NDManager.newBaseManager(TestUtils.getEngine())) {
+            NDArray array = manager.zeros(new Shape(3, 3, 4, 2));
+            NDArray result = array.pad(new Shape(1, 1), 1d);
+            Assert.assertEquals(result.getShape(), new Shape(3, 3, 4, 4));
+            Assert.assertEquals(result.getFloat(0, 0, 0, 0), 1f);
+
+            result = array.pad(new Shape(1, 1, 2, 2), 1d);
+            Assert.assertEquals(result.getShape(), new Shape(3, 3, 8, 4));
+
+            result = array.pad(new Shape(0, 1, 2, 1, 3, 3), 1d);
+            Assert.assertEquals(result.getShape(), new Shape(3, 9, 7, 3));
+        }
+    }
+
     @Test
     public void testReshape() {
         try (NDManager manager = NDManager.newBaseManager(TestUtils.getEngine())) {