Repository: deepjavalibrary/djl
Pull request #3309 (2024-07-08 23:03:17)

[onnxruntime] Adds yolov8n pose model for OnnxRuntime
## 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



Patch:
diff --git a/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java b/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java
index d18b6922b..0fd4f76c7 100644
--- a/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java
@@ -347,18 +347,37 @@ public class BufferedImageFactory extends ImageFactory {
             convertIdNeeded();
 
             Graphics2D g = (Graphics2D) image.getGraphics();
-            int stroke = 2;
-            g.setStroke(new BasicStroke(stroke));
+            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
 
             int imageWidth = image.getWidth();
             int imageHeight = image.getHeight();
 
-            for (Joints.Joint joint : joints.getJoints()) {
-                g.setPaint(randomColor().darker());
+            List<Joints.Joint> list = joints.getJoints();
+            if (list.size() == 17) {
+                g.setColor(new Color(224, 255, 37));
+                g.setStroke(new BasicStroke(3));
+                drawLine(g, list.get(5), list.get(7), imageWidth, imageHeight);
+                drawLine(g, list.get(7), list.get(9), imageWidth, imageHeight);
+                drawLine(g, list.get(6), list.get(8), imageWidth, imageHeight);
+                drawLine(g, list.get(8), list.get(10), imageWidth, imageHeight);
+                drawLine(g, list.get(11), list.get(13), imageWidth, imageHeight);
+                drawLine(g, list.get(12), list.get(14), imageWidth, imageHeight);
+                drawLine(g, list.get(13), list.get(15), imageWidth, imageHeight);
+                drawLine(g, list.get(14), list.get(16), imageWidth, imageHeight);
+                drawLine(g, list.get(5), list.get(6), imageWidth, imageHeight);
+                drawLine(g, list.get(11), list.get(12), imageWidth, imageHeight);
+                drawLine(g, list.get(5), list.get(11), imageWidth, imageHeight);
+                drawLine(g, list.get(6), list.get(12), imageWidth, imageHeight);
+            }
+
+            g.setColor(new Color(37, 150, 190));
+            g.setStroke(new BasicStroke(2));
+            for (Joints.Joint joint : list) {
                 int x = (int) (joint.getX() * imageWidth);
                 int y = (int) (joint.getY() * imageHeight);
-                g.fillOval(x, y, 10, 10);
+                g.fillOval(x - 6, y - 6, 12, 12);
             }
+
             g.dispose();
         }
 
@@ -380,8 +399,13 @@ public class BufferedImageFactory extends ImageFactory {
             image = target;
         }
 
-        private Color randomColor() {
-            return new Color(RandomUtils.nextInt(255));
+        private void drawLine(
+                Graphics2D g, Joints.Joint from, Joints.Joint to, int width, int height) {
+            int x0 = (int) (from.getX() * width);
+            int y0 = (int) (from.getY() * height);
+            int x1 = (int) (to.getX() * width);
+            int y1 = (int) (to.getY() * height);
+            g.drawLine(x0, y0, x1, y1);
         }
 
         private void drawText(Graphics2D g, String text, int x, int y, int stroke, int padding) {
@@ -421,13 +445,20 @@ public class BufferedImageFactory extends ImageFactory {
                 }
             }
             float[][] probDist = mask.getProbDist();
+            float max = 0;
+            for (float[] row : probDist) {
+                for (float f : row) {
+                    max = Math.max(max, f);
+                }
+            }
+            float ratio = 0.5f / max;
 
             BufferedImage maskImage =
                     new BufferedImage(
                             probDist[0].length, probDist.length, BufferedImage.TYPE_INT_ARGB);
             for (int yCor = 0; yCor < probDist.length; yCor++) {
                 for (int xCor = 0; xCor < probDist[0].length; xCor++) {
-                    float opacity = probDist[yCor][xCor] * 0.8f;
+                    float opacity = probDist[yCor][xCor] * ratio;
                     maskImage.setRGB(xCor, yCor, new Color(r, g, b, opacity).darker().getRGB());
                 }
             }
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
new file mode 100644
index 000000000..1d0ad777d
--- /dev/null
+++ b/api/src/main/java/ai/djl/modality/cv/translator/YoloSegmentationTranslator.java
@@ -0,0 +1,173 @@
+/*
+ * 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.translator;
+
+import ai.djl.modality.cv.output.BoundingBox;
+import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.output.Mask;
+import ai.djl.modality.cv.output.Rectangle;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.types.DataType;
+import ai.djl.translate.TranslatorContext;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/** A translator for Yolov8 instance segmentation models. */
+public class YoloSegmentationTranslator extends YoloV5Translator {
+
+    private static final int[] AXIS_0 = {0};
+    private static final int[] AXIS_1 = {1};
+
+    private float threshold;
+    private float nmsThreshold;
+    private int width;
+    private int height;
+
+    /**
+     * Creates the instance segmentation translator from the given builder.
+     *
+     * @param builder the builder for the translator
+     */
+    public YoloSegmentationTranslator(Builder builder) {
+        super(builder);
+        this.threshold = builder.threshold;
+        this.nmsThreshold = builder.nmsThreshold;
+        this.width = builder.width;
+        this.height = builder.height;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DetectedObjects processOutput(TranslatorContext ctx, NDList list) {
+        NDArray pred = list.get(0);
+        NDArray protos = list.get(1);
+        int maskIndex = classes.size() + 4;
+        NDArray candidates = pred.get("4:" + maskIndex).max(AXIS_0).gt(threshold);
+        pred = pred.transpose();
+        NDArray sub = pred.get("..., :4");
+        sub = xywh2xyxy(sub);
+        pred = sub.concat(pred.get("..., 4:"), -1);
+        pred = pred.get(candidates);
+
+        NDList split = pred.split(new long[] {4, maskIndex}, 1);
+        NDArray box = split.get(0);
+
+        int numBox = Math.toIntExact(box.getShape().get(0));
+
+        float[] buf = box.toFloatArray();
+        float[] confidences = split.get(1).max(AXIS_1).toFloatArray();
+        long[] ids = split.get(1).argMax(1).toLongArray();
+
+        List<Rectangle> boxes = new ArrayList<>(numBox);
+        List<Double> scores = new ArrayList<>(numBox);
+        for (int i = 0; i < numBox; ++i) {
+            float xPos = buf[i * 4];
+            float yPos = buf[i * 4 + 1];
+            float w = buf[i * 4 + 2] - xPos;
+            float h = buf[i * 4 + 3] - yPos;
+            Rectangle rect = new Rectangle(xPos, yPos, w, h);
+            boxes.add(rect);
+            scores.add((double) confidences[i]);
+        }
+        List<Integer> nms = Rectangle.nms(boxes, scores, nmsThreshold);
+        long[] idx = nms.stream().mapToLong(Integer::longValue).toArray();
+        NDArray selected = box.getManager().create(idx);
+        NDArray masks = split.get(2).get(selected);
+
+        int maskW = Math.toIntExact(protos.getShape().get(2));
+        int maskH = Math.toIntExact(protos.getShape().get(1));
+
+        protos = protos.reshape(32, (long) maskH * maskW);
+        masks =
+                masks.matMul(protos)
+                        .reshape(nms.size(), maskH, maskW)
+                        .gt(0f)
+                        .toType(DataType.FLOAT32, true);
+
+        float[] maskArray = masks.toFloatArray();
+        box = box.get(selected);
+        buf = box.toFloatArray();
+
+        List<String> retClasses = new ArrayList<>();
+        List<Double> retProbs = new ArrayList<>();
+        List<BoundingBox> retBB = new ArrayList<>();
+        for (int i = 0; i < idx.length; ++i) {
+            float x = buf[i * 4] / width;
+            float y = buf[i * 4 + 1] / height;
+            float w = buf[i * 4 + 2] / width - x;
+            float h = buf[i * 4 + 3] / width - y;
+            int id = nms.get(i);
+            retClasses.add(classes.get((int) ids[id]));
+            retProbs.add((double) confidences[id]);
+
+            float[][] maskFloat = new float[maskH][maskW];
+            for (int j = 0; j < maskH; j++) {
+                System.arraycopy(maskArray, j * maskW, maskFloat[j], 0, maskW);
+            }
+            Mask bb = new Mask(x, y, w, h, maskFloat, true);
+            retBB.add(bb);
+        }
+        return new DetectedObjects(retClasses, retProbs, retBB);
+    }
+
+    private NDArray xywh2xyxy(NDArray array) {
+        NDArray xy = array.get("..., :2");
+        NDArray wh = array.get("..., 2:").div(2);
+        return xy.sub(wh).concat(xy.add(wh), -1);
+    }
+
+    /**
+     * Creates a builder to build a {@code YoloSegmentationTranslator}.
+     *
+     * @return a new builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Creates a builder to build a {@code YoloSegmentationTranslator} with specified arguments.
+     *
+     * @param arguments arguments to specify builder options
+     * @return a new builder
+     */
+    public static Builder builder(Map<String, ?> arguments) {
+        Builder builder = new Builder();
+        builder.configPreProcess(arguments);
+        builder.configPostProcess(arguments);
+
+        return builder;
+    }
+
+    /** The builder for instance segmentation translator. */
+    public static class Builder extends YoloV5Translator.Builder {
+
+        Builder() {}
+
+        /** {@inheritDoc} */
+        @Override
+        protected Builder self() {
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public YoloSegmentationTranslator build() {
+            validate();
+            return new YoloSegmentationTranslator(this);
+        }
+    }
+}
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/YoloSegmentationTranslatorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/YoloSegmentationTranslatorFactory.java
new file mode 100644
index 000000000..f75fab003
--- /dev/null
+++ b/api/src/main/java/ai/djl/modality/cv/translator/YoloSegmentationTranslatorFactory.java
@@ -0,0 +1,35 @@
+/*
+ * 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.translator;
+
+import ai.djl.Model;
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.translate.Translator;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/** A translatorFactory that creates a {@link YoloSegmentationTranslator} instance. */
+public class YoloSegmentationTranslatorFactory extends ObjectDetectionTranslatorFactory
+        implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** {@inheritDoc} */
+    @Override
+    protected Translator<Image, DetectedObjects> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return YoloSegmentationTranslator.builder(arguments).build();
+    }
+}
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 7895ad15c..2101d9bbb 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
@@ -46,8 +46,8 @@ public class YoloV8Translator extends YoloV5Translator {
      *
      * @return a new builder
      */
-    public static YoloV8Translator.Builder builder() {
-        return new YoloV8Translator.Builder();
+    public static Builder builder() {
+        return new Builder();
     }
 
     /**
@@ -56,8 +56,8 @@ public class YoloV8Translator extends YoloV5Translator {
      * @param arguments arguments to specify builder options
      * @return a new builder
      */
-    public static YoloV8Translator.Builder builder(Map<String, ?> arguments) {
-        YoloV8Translator.Builder builder = new YoloV8Translator.Builder();
+    public static Builder builder(Map<String, ?> arguments) {
+        Builder builder = new Builder();
         builder.configPreProcess(arguments);
         builder.configPostProcess(arguments);
 
diff --git a/engines/onnxruntime/onnxruntime-engine/src/main/java/ai/djl/onnxruntime/zoo/OrtModelZoo.java b/engines/onnxruntime/onnxruntime-engine/src/main/java/ai/djl/onnxruntime/zoo/OrtModelZoo.java
index d61cb81f1..79dd9cb60 100644
--- a/engines/onnxruntime/onnxruntime-engine/src/main/java/ai/djl/onnxruntime/zoo/OrtModelZoo.java
+++ b/engines/onnxruntime/onnxruntime-engine/src/main/java/ai/djl/onnxruntime/zoo/OrtModelZoo.java
@@ -32,6 +32,7 @@ public class OrtModelZoo extends ModelZoo {
         addModel(REPOSITORY.model(CV.IMAGE_CLASSIFICATION, GROUP_ID, "resnet", "0.0.1"));
         addModel(REPOSITORY.model(CV.OBJECT_DETECTION, GROUP_ID, "yolo5s", "0.0.1"));
         addModel(REPOSITORY.model(CV.OBJECT_DETECTION, GROUP_ID, "yolov8n", "0.0.1"));
+        addModel(REPOSITORY.model(CV.POSE_ESTIMATION, GROUP_ID, "yolov8n-pose", "0.0.1"));
         addModel(REPOSITORY.model(Tabular.SOFTMAX_REGRESSION, GROUP_ID, "iris_flowers", "0.0.1"));
     }
 
diff --git a/engines/pytorch/pytorch-model-zoo/src/main/java/ai/djl/pytorch/zoo/PtModelZoo.java b/engines/pytorch/pytorch-model-zoo/src/main/java/ai/djl/pytorch/zoo/PtModelZoo.java
index 336e72e98..811d68706 100644
--- a/engines/pytorch/pytorch-model-zoo/src/main/java/ai/djl/pytorch/zoo/PtModelZoo.java
+++ b/engines/pytorch/pytorch-model-zoo/src/main/java/ai/djl/pytorch/zoo/PtModelZoo.java
@@ -42,6 +42,7 @@ public class PtModelZoo extends ModelZoo {
         addModel(REPOSITORY.model(CV.IMAGE_CLASSIFICATION, GROUP_ID, "resnet", "0.0.1"));
         addModel(
                 REPOSITORY.model(CV.IMAGE_CLASSIFICATION, GROUP_ID, "resnet18_embedding", "0.0.1"));
+        addModel(REPOSITORY.model(CV.INSTANCE_SEGMENTATION, GROUP_ID, "yolov8n-seg", "0.0.1"));
         addModel(REPOSITORY.model(CV.OBJECT_DETECTION, GROUP_ID, "ssd", "0.0.1"));
         addModel(REPOSITORY.model(CV.OBJECT_DETECTION, GROUP_ID, "yolov5s", "0.0.1"));
         addModel(REPOSITORY.model(CV.OBJECT_DETECTION, GROUP_ID, "yolov8n", "0.0.1"));
diff --git a/examples/src/main/java/ai/djl/examples/inference/cv/InstanceSegmentation.java b/examples/src/main/java/ai/djl/examples/inference/cv/InstanceSegmentation.java
index 057f15bf3..8a0aa804a 100644
--- a/examples/src/main/java/ai/djl/examples/inference/cv/InstanceSegmentation.java
+++ b/examples/src/main/java/ai/djl/examples/inference/cv/InstanceSegmentation.java
@@ -17,6 +17,7 @@ import ai.djl.inference.Predictor;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.ImageFactory;
 import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.translator.YoloSegmentationTranslatorFactory;
 import ai.djl.repository.zoo.Criteria;
 import ai.djl.repository.zoo.ZooModel;
 import ai.djl.training.util.ProgressBar;
@@ -55,9 +56,8 @@ public final class InstanceSegmentation {
         Criteria<Image, DetectedObjects> criteria =
                 Criteria.builder()
                         .setTypes(Image.class, DetectedObjects.class)
-                        .optModelUrls(
-                                "djl://ai.djl.mxnet/mask_rcnn/0.0.1/mask_rcnn_resnet18_v1b_coco")
-                        .optEngine("MXNet")
+                        .optModelUrls("djl://ai.djl.pytorch/yolov8n-seg")
+                        .optTranslatorFactory(new YoloSegmentationTranslatorFactory())
                         .optProgress(new ProgressBar())
                         .build();
 
diff --git a/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImage.java b/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImage.java
index 1cbacd634..14fd4112b 100644
--- a/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImage.java
+++ b/extensions/opencv/src/main/java/ai/djl/opencv/OpenCVImage.java
@@ -205,12 +205,25 @@ class OpenCVImage implements Image {
         int imageWidth = image.width();
         int imageHeight = image.height();
 
-        Scalar color =
-                new Scalar(
-                        RandomUtils.nextInt(178),
-                        RandomUtils.nextInt(178),
-                        RandomUtils.nextInt(178));
-        for (Joints.Joint joint : joints.getJoints()) {
+        List<Joints.Joint> list = joints.getJoints();
+        if (list.size() == 17) {
+            Scalar color = new Scalar(37, 255, 224);
+            drawLine(list.get(5), list.get(7), imageWidth, imageHeight, color);
+            drawLine(list.get(7), list.get(9), imageWidth, imageHeight, color);
+            drawLine(list.get(6), list.get(8), imageWidth, imageHeight, color);
+            drawLine(list.get(8), list.get(10), imageWidth, imageHeight, color);
+            drawLine(list.get(11), list.get(13), imageWidth, imageHeight, color);
+            drawLine(list.get(12), list.get(14), imageWidth, imageHeight, color);
+            drawLine(list.get(13), list.get(15), imageWidth, imageHeight, color);
+            drawLine(list.get(14), list.get(16), imageWidth, imageHeight, color);
+            drawLine(list.get(5), list.get(6), imageWidth, imageHeight, color);
+            drawLine(list.get(11), list.get(12), imageWidth, imageHeight, color);
+            drawLine(list.get(5), list.get(11), imageWidth, imageHeight, color);
+            drawLine(list.get(6), list.get(12), imageWidth, imageHeight, color);
+        }
+
+        Scalar color = new Scalar(190, 150, 37);
+        for (Joints.Joint joint : list) {
             int x = (int) (joint.getX() * imageWidth);
             int y = (int) (joint.getY() * imageHeight);
             Point point = new Point(x, y);
@@ -340,6 +353,14 @@ class OpenCVImage implements Image {
         return new OpenCVImage(result);
     }
 
+    private void drawLine(Joints.Joint from, Joints.Joint to, int width, int height, Scalar color) {
+        int x0 = (int) (from.getX() * width);
+        int y0 = (int) (from.getY() * height);
+        int x1 = (int) (to.getX() * width);
+        int y1 = (int) (to.getY() * height);
+        Imgproc.line(image, new Point(x0, y0), new Point(x1, y1), color, 2, Imgproc.LINE_AA);
+    }
+
     private void drawLandmarks(BoundingBox box) {
         Scalar color = new Scalar(0, 96, 246);
         for (ai.djl.modality.cv.output.Point point : box.getPath()) {
@@ -374,12 +395,19 @@ class OpenCVImage implements Image {
             }
         }
         float[][] probDist = mask.getProbDist();
+        float max = 0;
+        for (float[] row : probDist) {
+            for (float f : row) {
+                max = Math.max(max, f);
+            }
+        }
+        float ratio = 0.5f / max;
 
         BufferedImage maskImage =
                 new BufferedImage(probDist[0].length, probDist.length, BufferedImage.TYPE_INT_ARGB);
         for (int yCor = 0; yCor < probDist.length; yCor++) {
             for (int xCor = 0; xCor < probDist[0].length; xCor++) {
-                float opacity = probDist[yCor][xCor] * 0.8f;
+                float opacity = probDist[yCor][xCor] * ratio;
                 maskImage.setRGB(xCor, yCor, new Color(r, g, b, opacity).darker().getRGB());
             }
         }


Test patch:
diff --git a/engines/onnxruntime/onnxruntime-engine/src/test/resources/mlrepo/model/cv/pose_estimation/ai/djl/onnxruntime/yolov8n-pose/metadata.json b/engines/onnxruntime/onnxruntime-engine/src/test/resources/mlrepo/model/cv/pose_estimation/ai/djl/onnxruntime/yolov8n-pose/metadata.json
new file mode 100644
index 000000000..fd24c60fa
--- /dev/null
+++ b/engines/onnxruntime/onnxruntime-engine/src/test/resources/mlrepo/model/cv/pose_estimation/ai/djl/onnxruntime/yolov8n-pose/metadata.json
@@ -0,0 +1,40 @@
+{
+  "metadataVersion": "0.2",
+  "resourceType": "model",
+  "application": "cv/pose_estimation",
+  "groupId": "ai.djl.onnxruntime",
+  "artifactId": "yolov8n-pose",
+  "name": "Yolov8n pose",
+  "description": "Yolov8n Pose Estimation",
+  "website": "http://www.djl.ai/engines/onnxruntime/onnxruntime-engine",
+  "licenses": {
+    "license": {
+      "name": "The Apache License, Version 2.0",
+      "url": "https://www.apache.org/licenses/LICENSE-2.0"
+    }
+  },
+  "artifacts": [
+    {
+      "version": "0.0.1",
+      "snapshot": false,
+      "name": "yolov8n-pose",
+      "properties": {
+      },
+      "arguments": {
+        "width": 640,
+        "height": 640,
+        "resize": true,
+        "threshold": 0.25,
+        "translatorFactory": "ai.djl.modality.cv.translator.YoloPoseTranslatorFactory"
+      },
+      "files": {
+        "model": {
+          "uri": "0.0.1/yolov8n-pose.zip",
+          "sha1Hash": "8b968172ad2dd80a40fd16a276274ee8e2a2ff44",
+          "name": "",
+          "size": 11535121
+        }
+      }
+    }
+  ]
+}
diff --git a/engines/pytorch/pytorch-model-zoo/src/test/resources/mlrepo/model/cv/instance_segmentation/ai/djl/pytorch/yolov8n-seg/metadata.json b/engines/pytorch/pytorch-model-zoo/src/test/resources/mlrepo/model/cv/instance_segmentation/ai/djl/pytorch/yolov8n-seg/metadata.json
new file mode 100644
index 000000000..74d9ca6c1
--- /dev/null
+++ b/engines/pytorch/pytorch-model-zoo/src/test/resources/mlrepo/model/cv/instance_segmentation/ai/djl/pytorch/yolov8n-seg/metadata.json
@@ -0,0 +1,43 @@
+{
+  "metadataVersion": "0.2",
+  "resourceType": "model",
+  "application": "cv/instance_segmentation",
+  "groupId": "ai.djl.pytorch",
+  "artifactId": "yolov8n-seg",
+  "name": "Yolov8n seg",
+  "description": "Yolov8n Instance Segmentation",
+  "website": "http://www.djl.ai/engines/pytorch/pytorch-model-zoo",
+  "licenses": {
+    "license": {
+      "name": "The Apache License, Version 2.0",
+      "url": "https://www.apache.org/licenses/LICENSE-2.0"
+    }
+  },
+  "artifacts": [
+    {
+      "version": "0.0.1",
+      "snapshot": false,
+      "name": "yolov8n-seg",
+      "properties": {
+      },
+      "arguments": {
+        "width": 640,
+        "height": 640,
+        "resize": true,
+        "threshold": 0.25,
+        "translatorFactory": "ai.djl.modality.cv.translator.YoloSegmentationTranslatorFactory"
+      },
+      "options": {
+        "mapLocation": "true"
+      },
+      "files": {
+        "model": {
+          "uri": "0.0.1/yolov8n-seg.zip",
+          "sha1Hash": "bbb0d99b20b3235d70547617110d58b94e28c124",
+          "name": "",
+          "size": 12069002
+        }
+      }
+    }
+  ]
+}
diff --git a/examples/src/test/java/ai/djl/examples/inference/InstanceSegmentationTest.java b/examples/src/test/java/ai/djl/examples/inference/InstanceSegmentationTest.java
index 1cf6c06d7..e04f0f99b 100644
--- a/examples/src/test/java/ai/djl/examples/inference/InstanceSegmentationTest.java
+++ b/examples/src/test/java/ai/djl/examples/inference/InstanceSegmentationTest.java
@@ -16,7 +16,6 @@ import ai.djl.ModelException;
 import ai.djl.examples.inference.cv.InstanceSegmentation;
 import ai.djl.modality.Classifications;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.testing.TestRequirements;
 import ai.djl.translate.TranslateException;
 
 import org.testng.Assert;
@@ -28,11 +27,9 @@ public class InstanceSegmentationTest {
 
     @Test
     public void testInstanceSegmentation() throws ModelException, TranslateException, IOException {
-        TestRequirements.linux();
-
         DetectedObjects result = InstanceSegmentation.predict();
         Classifications.Classification best = result.best();
         Assert.assertEquals(best.getClassName(), "bicycle");
-        Assert.assertTrue(Double.compare(best.getProbability(), 0.9) > 0);
+        Assert.assertTrue(Double.compare(best.getProbability(), 0.7) > 0);
     }
 }