Repository: deepjavalibrary/djl
Pull request #3454 (2024-09-04 22:36:53)

[api] Updates dependencies version to latest
## 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/ImageFactory.java b/api/src/main/java/ai/djl/modality/cv/ImageFactory.java
index 4868e15a9..ab11a6fee 100644
--- a/api/src/main/java/ai/djl/modality/cv/ImageFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/ImageFactory.java
@@ -17,12 +17,16 @@ import ai.djl.ndarray.NDArray;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URL;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Base64;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * {@code ImageFactory} contains image creation mechanism on top of different platforms like PC and
@@ -38,6 +42,8 @@ public abstract class ImageFactory {
         "ai.djl.android.core.BitmapImageFactory"
     };
 
+    private static final Pattern URL_PATTERN = Pattern.compile("^data:image/\\w+;base64,(.+)");
+
     private static ImageFactory factory = newInstance();
 
     private static ImageFactory newInstance() {
@@ -98,13 +104,22 @@ public abstract class ImageFactory {
     }
 
     /**
-     * Gets {@link Image} from URL.
+     * Gets {@link Image} from string representation.
      *
-     * @param url the String represent URL to load from
+     * @param url the String represent URL or base64 encoded image to load from
      * @return {@link Image}
      * @throws IOException URL is not valid.
      */
     public Image fromUrl(String url) throws IOException {
+        Matcher m = URL_PATTERN.matcher(url);
+        if (m.matches()) {
+            // url="data:image/png;base64,..."
+            byte[] buf = Base64.getDecoder().decode(m.group(1));
+            try (InputStream is = new ByteArrayInputStream(buf)) {
+                return fromInputStream(is);
+            }
+        }
+
         URI uri = URI.create(url);
         if (uri.isAbsolute()) {
             return fromUrl(uri.toURL());
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslatorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslatorFactory.java
index d48176aa4..59b14f528 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslatorFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslatorFactory.java
@@ -15,8 +15,9 @@ package ai.djl.modality.cv.translator;
 import ai.djl.modality.Input;
 import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.translator.wrapper.FileImagePreProcesor;
+import ai.djl.modality.cv.translator.wrapper.FileImagePreProcessor;
 import ai.djl.modality.cv.translator.wrapper.InputStreamImagePreProcessor;
+import ai.djl.modality.cv.translator.wrapper.StringImagePreProcessor;
 import ai.djl.modality.cv.translator.wrapper.UrlImagePreProcessor;
 import ai.djl.translate.ExpansionTranslatorFactory;
 import ai.djl.translate.PreProcessor;
@@ -44,8 +45,9 @@ public abstract class BaseImageTranslatorFactory<O> extends ExpansionTranslatorF
             getPreprocessorExpansions() {
         Map<Type, Function<PreProcessor<Image>, PreProcessor<?>>> expansions =
                 new ConcurrentHashMap<>();
-        expansions.put(Path.class, FileImagePreProcesor::new);
+        expansions.put(Path.class, FileImagePreProcessor::new);
         expansions.put(URL.class, UrlImagePreProcessor::new);
+        expansions.put(String.class, StringImagePreProcessor::new);
         expansions.put(InputStream.class, InputStreamImagePreProcessor::new);
         return expansions;
     }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractor.java b/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractor.java
index 8edde7a34..cb19ae740 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractor.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractor.java
@@ -13,6 +13,7 @@
 package ai.djl.modality.cv.translator;
 
 import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.types.DataType;
 import ai.djl.translate.TranslatorContext;
 
 import java.util.Map;
@@ -20,7 +21,7 @@ import java.util.Map;
 /**
  * A generic {@link ai.djl.translate.Translator} for Image Classification feature extraction tasks.
  */
-public class ImageFeatureExtractor extends BaseImageTranslator<byte[]> {
+public class ImageFeatureExtractor extends BaseImageTranslator<float[]> {
 
     /**
      * Constructs an Image Classification using {@link Builder}.
@@ -33,8 +34,8 @@ public class ImageFeatureExtractor extends BaseImageTranslator<byte[]> {
 
     /** {@inheritDoc} */
     @Override
-    public byte[] processOutput(TranslatorContext ctx, NDList list) {
-        return list.get(0).toByteArray();
+    public float[] processOutput(TranslatorContext ctx, NDList list) {
+        return list.get(0).toType(DataType.FLOAT32, false).toFloatArray();
     }
 
     /**
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractorFactory.java
index 82e9344a1..620b1a378 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractorFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractorFactory.java
@@ -21,20 +21,21 @@ import java.io.Serializable;
 import java.util.Map;
 
 /** A {@link TranslatorFactory} that creates an {@link ImageClassificationTranslator}. */
-public class ImageFeatureExtractorFactory extends BaseImageTranslatorFactory<byte[]>
+public class ImageFeatureExtractorFactory extends BaseImageTranslatorFactory<float[]>
         implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
     /** {@inheritDoc} */
     @Override
-    protected Translator<Image, byte[]> buildBaseTranslator(Model model, Map<String, ?> arguments) {
+    protected Translator<Image, float[]> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
         return ImageFeatureExtractor.builder(arguments).build();
     }
 
     /** {@inheritDoc} */
     @Override
-    public Class<byte[]> getBaseOutputType() {
-        return byte[].class;
+    public Class<float[]> getBaseOutputType() {
+        return float[].class;
     }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/ImageServingTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/ImageServingTranslator.java
index 4df9b7e2f..688662a6d 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/ImageServingTranslator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/ImageServingTranslator.java
@@ -23,6 +23,12 @@ import ai.djl.translate.TranslateException;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorContext;
 import ai.djl.util.JsonSerializable;
+import ai.djl.util.JsonUtils;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -80,7 +86,34 @@ public class ImageServingTranslator implements Translator<Input, Output> {
             if (data == null) {
                 throw new TranslateException("Input data is empty.");
             }
-            Image image = factory.fromInputStream(new ByteArrayInputStream(data.getAsBytes()));
+            String contentType = input.getProperty("Content-Type", null);
+            if (contentType != null) {
+                int pos = contentType.indexOf(';');
+                if (pos > 0) {
+                    contentType = contentType.substring(0, pos);
+                }
+            }
+            Image image;
+            if ("application/json".equalsIgnoreCase(contentType)) {
+                try {
+                    JsonElement element =
+                            JsonUtils.GSON.fromJson(data.getAsString(), JsonElement.class);
+                    if (element == null || !element.isJsonObject()) {
+                        throw new TranslateException("Invalid JsonObject input.");
+                    }
+                    JsonObject obj = element.getAsJsonObject();
+                    JsonPrimitive url = obj.getAsJsonPrimitive("image_url");
+                    if (url == null) {
+                        throw new TranslateException("Missing \"image_url\" in json.");
+                    }
+
+                    image = factory.fromUrl(url.getAsString());
+                } catch (JsonParseException e) {
+                    throw new TranslateException("Input is not a valid json.", e);
+                }
+            } else {
+                image = factory.fromInputStream(new ByteArrayInputStream(data.getAsBytes()));
+            }
             return translator.processInput(ctx, image);
         } catch (IOException e) {
             throw new TranslateException("Input is not an Image data type", e);
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileImagePreProcesor.java b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileImagePreProcessor.java
similarity index 82%
rename from api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileImagePreProcesor.java
rename to api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileImagePreProcessor.java
index ad91e8f52..1f611ad81 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileImagePreProcesor.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileImagePreProcessor.java
@@ -21,16 +21,16 @@ import ai.djl.translate.TranslatorContext;
 import java.nio.file.Path;
 
 /** Built-in {@code PreProcessor} that provides image pre-processing from file path. */
-public class FileImagePreProcesor implements PreProcessor<Path> {
+public class FileImagePreProcessor implements PreProcessor<Path> {
 
     private PreProcessor<Image> preProcessor;
 
     /**
-     * Creates a {@code FileImagePreProcesor} instance.
+     * Creates a {@code FileImagePreProcessor} instance.
      *
-     * @param preProcessor a {@code {@link PreProcessor}} that can process image
+     * @param preProcessor a {@code PreProcessor} that can process image
      */
-    public FileImagePreProcesor(PreProcessor<Image> preProcessor) {
+    public FileImagePreProcessor(PreProcessor<Image> preProcessor) {
         this.preProcessor = preProcessor;
     }
 
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/StringImagePreProcessor.java b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/StringImagePreProcessor.java
new file mode 100644
index 000000000..8532ee3a4
--- /dev/null
+++ b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/StringImagePreProcessor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.wrapper;
+
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.ImageFactory;
+import ai.djl.ndarray.NDList;
+import ai.djl.translate.PreProcessor;
+import ai.djl.translate.TranslatorContext;
+
+/**
+ * Built-in {@code PreProcessor} that provides image pre-processing from url or base64 encoded
+ * string.
+ */
+public class StringImagePreProcessor implements PreProcessor<String> {
+
+    private PreProcessor<Image> preProcessor;
+
+    /**
+     * Creates a {@code StringImagePreProcessor} instance.
+     *
+     * @param preProcessor a {@code PreProcessor} that can process image
+     */
+    public StringImagePreProcessor(PreProcessor<Image> preProcessor) {
+        this.preProcessor = preProcessor;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NDList processInput(TranslatorContext ctx, String input) throws Exception {
+        Image image = ImageFactory.getInstance().fromUrl(input);
+        return preProcessor.processInput(ctx, image);
+    }
+}
diff --git a/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbNDManager.java b/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbNDManager.java
index d1406b140..f21943bb9 100644
--- a/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbNDManager.java
+++ b/engines/ml/xgboost/src/main/java/ai/djl/ml/xgboost/XgbNDManager.java
@@ -147,10 +147,13 @@ public class XgbNDManager extends BaseNDManager {
             throw new UnsupportedOperationException("XgbNDArray only supports float32.");
         }
 
-        if (data.isDirect() && data instanceof ByteBuffer) {
-            // TODO: allow user to set missing value
-            long handle = JniUtils.createDMatrix(data, shape, missingValue);
-            return new XgbNDArray(this, alternativeManager, handle, shape, SparseFormat.DENSE);
+        if (data instanceof ByteBuffer) {
+            if (data.isDirect()) {
+                // TODO: allow user to set missing value
+                long handle = JniUtils.createDMatrix(data, shape, missingValue);
+                return new XgbNDArray(this, alternativeManager, handle, shape, SparseFormat.DENSE);
+            }
+            data = ((ByteBuffer) data).asFloatBuffer();
         }
 
         DataType inputType = DataType.fromBuffer(data);
diff --git a/examples/src/main/java/ai/djl/examples/inference/cv/ActionRecognition.java b/examples/src/main/java/ai/djl/examples/inference/cv/ActionRecognition.java
index a069ed358..ba90f45ad 100644
--- a/examples/src/main/java/ai/djl/examples/inference/cv/ActionRecognition.java
+++ b/examples/src/main/java/ai/djl/examples/inference/cv/ActionRecognition.java
@@ -15,8 +15,6 @@ package ai.djl.examples.inference.cv;
 import ai.djl.ModelException;
 import ai.djl.inference.Predictor;
 import ai.djl.modality.Classifications;
-import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.ImageFactory;
 import ai.djl.repository.zoo.Criteria;
 import ai.djl.repository.zoo.ZooModel;
 import ai.djl.training.util.ProgressBar;
@@ -26,6 +24,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.net.URL;
 
 /**
  * An example of inference using an action recognition model.
@@ -46,22 +45,20 @@ public final class ActionRecognition {
     }
 
     public static Classifications predict() throws IOException, ModelException, TranslateException {
-        String url = "https://resources.djl.ai/images/action_dance.jpg";
-        Image img = ImageFactory.getInstance().fromUrl(url);
-
+        URL url = new URL("https://resources.djl.ai/images/action_dance.jpg");
         // Use DJL PyTorch model zoo model
-        Criteria<Image, Classifications> criteria =
+        Criteria<URL, Classifications> criteria =
                 Criteria.builder()
-                        .setTypes(Image.class, Classifications.class)
+                        .setTypes(URL.class, Classifications.class)
                         .optModelUrls(
                                 "djl://ai.djl.pytorch/Human-Action-Recognition-VIT-Base-patch16-224")
                         .optEngine("PyTorch")
                         .optProgress(new ProgressBar())
                         .build();
 
-        try (ZooModel<Image, Classifications> inception = criteria.loadModel();
-                Predictor<Image, Classifications> action = inception.newPredictor()) {
-            return action.predict(img);
+        try (ZooModel<URL, Classifications> inception = criteria.loadModel();
+                Predictor<URL, Classifications> action = inception.newPredictor()) {
+            return action.predict(url);
         }
     }
 }
diff --git a/examples/src/main/java/ai/djl/examples/inference/cv/MaskDetection.java b/examples/src/main/java/ai/djl/examples/inference/cv/MaskDetection.java
index b9d71ed67..9de62f05e 100644
--- a/examples/src/main/java/ai/djl/examples/inference/cv/MaskDetection.java
+++ b/examples/src/main/java/ai/djl/examples/inference/cv/MaskDetection.java
@@ -56,9 +56,9 @@ public final class MaskDetection {
 
         // modelUrl can be replaced to local onnx model file
         String modelUrl = "https://resources.djl.ai/demo/onnxruntime/face_mask_detection.zip";
-        Criteria<Image, DetectedObjects> criteria =
+        Criteria<String, DetectedObjects> criteria =
                 Criteria.builder()
-                        .setTypes(Image.class, DetectedObjects.class)
+                        .setTypes(String.class, DetectedObjects.class)
                         .optModelUrls(modelUrl)
                         .optEngine("OnnxRuntime")
                         .optTranslatorFactory(new YoloV5TranslatorFactory())
@@ -67,9 +67,9 @@ public final class MaskDetection {
                         .optArgument("rescale", true) // post process
                         .build();
 
-        try (ZooModel<Image, DetectedObjects> model = criteria.loadModel()) {
-            try (Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
-                DetectedObjects detection = predictor.predict(img);
+        try (ZooModel<String, DetectedObjects> model = criteria.loadModel()) {
+            try (Predictor<String, DetectedObjects> predictor = model.newPredictor()) {
+                DetectedObjects detection = predictor.predict(imageUrl);
                 String outputDir = "build/output";
                 saveBoundingBoxImage(img, detection, outputDir);
                 return detection;
diff --git a/examples/src/main/java/ai/djl/examples/inference/cv/Yolov8Detection.java b/examples/src/main/java/ai/djl/examples/inference/cv/Yolov8Detection.java
index 5aee1246c..eab5bdbfc 100644
--- a/examples/src/main/java/ai/djl/examples/inference/cv/Yolov8Detection.java
+++ b/examples/src/main/java/ai/djl/examples/inference/cv/Yolov8Detection.java
@@ -50,9 +50,9 @@ public final class Yolov8Detection {
 
         // Use DJL OnnxRuntime model zoo model, model can be found:
         // https://mlrepo.djl.ai/model/cv/object_detection/ai/djl/onnxruntime/yolov8n/0.0.1/yolov8n.zip
-        Criteria<Image, DetectedObjects> criteria =
+        Criteria<Path, DetectedObjects> criteria =
                 Criteria.builder()
-                        .setTypes(Image.class, DetectedObjects.class)
+                        .setTypes(Path.class, DetectedObjects.class)
                         .optModelUrls("djl://ai.djl.onnxruntime/yolov8n")
                         .optEngine("OnnxRuntime")
                         .optArgument("width", 640)
@@ -68,12 +68,12 @@ public final class Yolov8Detection {
                         .optProgress(new ProgressBar())
                         .build();
 
-        try (ZooModel<Image, DetectedObjects> model = criteria.loadModel();
-                Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
+        try (ZooModel<Path, DetectedObjects> model = criteria.loadModel();
+                Predictor<Path, DetectedObjects> predictor = model.newPredictor()) {
             Path outputPath = Paths.get("build/output");
             Files.createDirectories(outputPath);
 
-            DetectedObjects detection = predictor.predict(img);
+            DetectedObjects detection = predictor.predict(imgPath);
             if (detection.getNumberOfObjects() > 0) {
                 img.drawBoundingBoxes(detection);
                 Path output = outputPath.resolve("yolov8_detected.png");
diff --git a/examples/src/main/java/ai/djl/examples/inference/face/FeatureExtraction.java b/examples/src/main/java/ai/djl/examples/inference/face/FeatureExtraction.java
index 550cecdc3..f941fb58b 100644
--- a/examples/src/main/java/ai/djl/examples/inference/face/FeatureExtraction.java
+++ b/examples/src/main/java/ai/djl/examples/inference/face/FeatureExtraction.java
@@ -16,17 +16,11 @@ import ai.djl.ModelException;
 import ai.djl.inference.Predictor;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.ImageFactory;
-import ai.djl.modality.cv.transform.Normalize;
-import ai.djl.modality.cv.transform.ToTensor;
-import ai.djl.ndarray.NDArray;
-import ai.djl.ndarray.NDList;
+import ai.djl.modality.cv.translator.ImageFeatureExtractorFactory;
 import ai.djl.repository.zoo.Criteria;
 import ai.djl.repository.zoo.ZooModel;
 import ai.djl.training.util.ProgressBar;
-import ai.djl.translate.Pipeline;
 import ai.djl.translate.TranslateException;
-import ai.djl.translate.Translator;
-import ai.djl.translate.TranslatorContext;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,6 +29,8 @@ import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
 public final class FeatureExtraction {
 
@@ -55,13 +51,25 @@ public final class FeatureExtraction {
     public static float[] predict(Image img)
             throws IOException, ModelException, TranslateException {
         img.getWrappedImage();
+
+        List<Float> mean =
+                Arrays.asList(
+                        127.5f / 255.0f,
+                        127.5f / 255.0f,
+                        127.5f / 255.0f,
+                        128.0f / 255.0f,
+                        128.0f / 255.0f,
+                        128.0f / 255.0f);
+        String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
+
         Criteria<Image, float[]> criteria =
                 Criteria.builder()
                         .setTypes(Image.class, float[].class)
                         .optModelUrls(
                                 "https://resources.djl.ai/test-models/pytorch/face_feature.zip")
                         .optModelName("face_feature") // specify model file prefix
-                        .optTranslator(new FaceFeatureTranslator())
+                        .optArgument("normalize", normalize)
+                        .optTranslatorFactory(new ImageFeatureExtractorFactory())
                         .optProgress(new ProgressBar())
                         .optEngine("PyTorch") // Use PyTorch engine
                         .build();
@@ -71,44 +79,4 @@ public final class FeatureExtraction {
             return predictor.predict(img);
         }
     }
-
-    private static final class FaceFeatureTranslator implements Translator<Image, float[]> {
-
-        FaceFeatureTranslator() {}
-
-        /** {@inheritDoc} */
-        @Override
-        public NDList processInput(TranslatorContext ctx, Image input) {
-            NDArray array = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR);
-            Pipeline pipeline = new Pipeline();
-            pipeline
-                    // .add(new Resize(160))
-                    .add(new ToTensor())
-                    .add(
-                            new Normalize(
-                                    new float[] {127.5f / 255.0f, 127.5f / 255.0f, 127.5f / 255.0f},
-                                    new float[] {
-                                        128.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f
-                                    }));
-
-            return pipeline.transform(new NDList(array));
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public float[] processOutput(TranslatorContext ctx, NDList list) {
-            NDList result = new NDList();
-            long numOutputs = list.singletonOrThrow().getShape().get(0);
-            for (int i = 0; i < numOutputs; i++) {
-                result.add(list.singletonOrThrow().get(i));
-            }
-            float[][] embeddings =
-                    result.stream().map(NDArray::toFloatArray).toArray(float[][]::new);
-            float[] feature = new float[embeddings.length];
-            for (int i = 0; i < embeddings.length; i++) {
-                feature[i] = embeddings[i][0];
-            }
-            return feature;
-        }
-    }
 }
diff --git a/extensions/tokenizers/rust/Cargo.toml b/extensions/tokenizers/rust/Cargo.toml
index 507ae35a4..c177a6681 100644
--- a/extensions/tokenizers/rust/Cargo.toml
+++ b/extensions/tokenizers/rust/Cargo.toml
@@ -27,6 +27,8 @@ candle = { git = "https://github.com/huggingface/candle", rev = "f76bb7794aa8659
 candle-nn = { git = "https://github.com/huggingface/candle", rev = "f76bb7794aa8659c5023797979a3392cdfc01f32", package = "candle-nn" }
 candle-transformers = { git = "https://github.com/huggingface/candle", rev = "f76bb7794aa8659c5023797979a3392cdfc01f32", package = "candle-transformers" }
 candle-flash-attn = { git = "https://github.com/huggingface/candle", rev = "f76bb7794aa8659c5023797979a3392cdfc01f32", package = "candle-flash-attn" }
+# openssl-src 300.3.2 is broken for AL2
+openssl-src = { git = "https://github.com/alexcrichton/openssl-src-rs", rev = "c591de676017446c9f1d719e77eb162159ce8886", package = "openssl-src" }
 
 [target.'cfg(target_os = "linux")'.dependencies]
 openssl = { version = "0.10", features = ["vendored"] }
diff --git a/extensions/tokenizers/src/main/python/djl_converter/arg_parser.py b/extensions/tokenizers/src/main/python/djl_converter/arg_parser.py
index a7a0edad3..0b02d4d5b 100644
--- a/extensions/tokenizers/src/main/python/djl_converter/arg_parser.py
+++ b/extensions/tokenizers/src/main/python/djl_converter/arg_parser.py
@@ -78,6 +78,16 @@ def importer_args():
                         "--cpu-only",
                         action='store_true',
                         help="Only validate jit traced model on CPU")
+    parser.add_argument("--optimize",
+                        choices=["O1", "O2", "O3", "O4"],
+                        help="Optimization option for ONNX models")
+    parser.add_argument(
+        "--device",
+        help='The device to use to do the export. Defaults to "cpu".')
+    parser.add_argument(
+        "--dtype",
+        choices=["fp32", "fp16", "bf16"],
+        help="The floating point precision to use for the export.")
     group = parser.add_mutually_exclusive_group(required=True)
     group.add_argument(
         "-c",
diff --git a/extensions/tokenizers/src/main/python/djl_converter/zip_utils.py b/extensions/tokenizers/src/main/python/djl_converter/zip_utils.py
index 6445f1272..648b70935 100644
--- a/extensions/tokenizers/src/main/python/djl_converter/zip_utils.py
+++ b/extensions/tokenizers/src/main/python/djl_converter/zip_utils.py
@@ -23,5 +23,5 @@ def add_to_zip(path: str, handle: zipfile.ZipFile):
 
 
 def zip_dir(src_dir: str, output_file: str):
-    with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as f:
+    with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_STORED) as f:
         add_to_zip(src_dir, f)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7ac16b108..0ceae7b4e 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -14,34 +14,34 @@ lightgbm = "3.2.110"
 tensorrt = "8.4.1"
 fasttext = "0.9.2"
 sentencepiece = "0.2.0"
-tokenizers = "0.19.1"
+tokenizers = "0.20.0"
 
-slf4j = "2.0.13"
+slf4j = "2.0.16"
 log4j_slf4j = "2.23.1"
 gson = "2.11.0"
 jna = "5.14.0"
-commonCli = "1.8.0"
-commonsCompress = "1.26.2"
+commonCli = "1.9.0"
+commonsCompress = "1.27.1"
 commonsCsv = "1.11.0"
-commonsLogging = "1.3.3"
+commonsLogging = "1.3.4"
 
-awssdk = "2.26.19"
+awssdk = "2.27.18"
 ffmpeg = "6.1.1-1.5.10"
 hadoop = "3.4.0"
 javacpp = "1.5.10"
 javacv = "1.5.10"
 openpnpOpencv = "4.9.0-0"
-spark = "3.5.1"
+spark = "3.5.2"
 tablesaw = "0.43.1"
 rapis = "22.12.0"
-protobuf = "3.25.3"
+protobuf = "3.25.4"
 jackson = "2.17.2"
 jtransforms = "3.1"
 
 antlr = "4.11.1"
 testng = "7.10.2"
 junit = "4.13.2"
-mockito = "5.12.0"
+mockito = "5.13.0"
 
 [libraries]
 slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }


Test patch:
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java
index 5427cebe7..3bb221f01 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java
@@ -41,7 +41,7 @@ public class ImageClassificationTranslatorFactoryTest {
 
     @Test
     public void testGetSupportedTypes() {
-        Assert.assertEquals(factory.getSupportedTypes().size(), 5);
+        Assert.assertEquals(factory.getSupportedTypes().size(), 6);
     }
 
     @Test
@@ -68,6 +68,10 @@ public class ImageClassificationTranslatorFactoryTest {
                     factory.newInstance(Input.class, Output.class, model, arguments);
             Assert.assertTrue(translator5 instanceof ImageServingTranslator);
 
+            Translator<String, Classifications> translator6 =
+                    factory.newInstance(String.class, Classifications.class, model, arguments);
+            Assert.assertTrue(translator6 instanceof BasicTranslator);
+
             Assert.assertThrows(
                     IllegalArgumentException.class,
                     () -> factory.newInstance(Image.class, Output.class, model, arguments));
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java
index 21eaedadc..9ee64c172 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java
@@ -41,7 +41,7 @@ public class InstanceSegmentationTranslatorFactoryTest {
 
     @Test
     public void testGetSupportedTypes() {
-        Assert.assertEquals(factory.getSupportedTypes().size(), 5);
+        Assert.assertEquals(factory.getSupportedTypes().size(), 6);
     }
 
     @Test
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java
index cfbcfb829..103ef382d 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java
@@ -41,7 +41,7 @@ public class YoloTranslatorFactoryTest {
 
     @Test
     public void testGetSupportedTypes() {
-        Assert.assertEquals(factory.getSupportedTypes().size(), 5);
+        Assert.assertEquals(factory.getSupportedTypes().size(), 6);
     }
 
     @Test
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java
index 1ac8bf7b8..77b566be2 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java
@@ -41,7 +41,7 @@ public class YoloV5TranslatorFactoryTest {
 
     @Test
     public void testGetSupportedTypes() {
-        Assert.assertEquals(factory.getSupportedTypes().size(), 5);
+        Assert.assertEquals(factory.getSupportedTypes().size(), 6);
     }
 
     @Test
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/YoloV8TranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/YoloV8TranslatorFactoryTest.java
index 8fbbae730..c6702497f 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/YoloV8TranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/YoloV8TranslatorFactoryTest.java
@@ -41,7 +41,7 @@ public class YoloV8TranslatorFactoryTest {
 
     @Test
     public void testGetSupportedTypes() {
-        Assert.assertEquals(factory.getSupportedTypes().size(), 5);
+        Assert.assertEquals(factory.getSupportedTypes().size(), 6);
     }
 
     @Test
diff --git a/examples/src/test/java/ai/djl/examples/inference/face/FeatureExtractionTest.java b/examples/src/test/java/ai/djl/examples/inference/face/FeatureExtractionTest.java
index af50b240a..e3c5cbe1f 100644
--- a/examples/src/test/java/ai/djl/examples/inference/face/FeatureExtractionTest.java
+++ b/examples/src/test/java/ai/djl/examples/inference/face/FeatureExtractionTest.java
@@ -15,7 +15,6 @@ package ai.djl.examples.inference.face;
 import ai.djl.ModelException;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.ImageFactory;
-import ai.djl.testing.TestRequirements;
 import ai.djl.translate.TranslateException;
 
 import org.testng.Assert;
@@ -29,11 +28,24 @@ public class FeatureExtractionTest {
 
     @Test
     public void testFeatureComparison() throws ModelException, TranslateException, IOException {
-        TestRequirements.linux();
-
         Path imageFile = Paths.get("src/test/resources/kana1.jpg");
         Image img = ImageFactory.getInstance().fromFile(imageFile);
         float[] feature = FeatureExtraction.predict(img);
         Assert.assertEquals(feature.length, 512);
+        float[] expected = {
+            -0.040261813f,
+            -0.019486334f,
+            -0.09802657f,
+            0.017009983f,
+            0.037828982f,
+            0.030801114f,
+            -0.02714689f,
+            0.042024296f,
+            -0.009838469f,
+            -0.005961003f
+        };
+        float[] sub = new float[10];
+        System.arraycopy(feature, 0, sub, 0, 10);
+        Assert.assertEquals(sub, expected, 0.0001f);
     }
 }
diff --git a/extensions/tokenizers/src/test/java/ai/djl/huggingface/tokenizers/HuggingFaceTokenizerTest.java b/extensions/tokenizers/src/test/java/ai/djl/huggingface/tokenizers/HuggingFaceTokenizerTest.java
index 713e1fbeb..5c1e63873 100644
--- a/extensions/tokenizers/src/test/java/ai/djl/huggingface/tokenizers/HuggingFaceTokenizerTest.java
+++ b/extensions/tokenizers/src/test/java/ai/djl/huggingface/tokenizers/HuggingFaceTokenizerTest.java
@@ -38,7 +38,7 @@ public class HuggingFaceTokenizerTest {
     public void testVersion() {
         try (HuggingFaceTokenizer tokenizer = HuggingFaceTokenizer.newInstance("bert-base-cased")) {
             String djlVersion = Engine.getDjlVersion();
-            Assert.assertEquals(tokenizer.getVersion(), "0.19.1-" + djlVersion);
+            Assert.assertEquals(tokenizer.getVersion(), "0.20.0-" + djlVersion);
         }
     }
 
diff --git a/integration/src/main/java/ai/djl/integration/tests/modality/cv/BufferedImageFactoryTest.java b/integration/src/main/java/ai/djl/integration/tests/modality/cv/BufferedImageFactoryTest.java
index fcc9fd3a1..6a010c896 100644
--- a/integration/src/main/java/ai/djl/integration/tests/modality/cv/BufferedImageFactoryTest.java
+++ b/integration/src/main/java/ai/djl/integration/tests/modality/cv/BufferedImageFactoryTest.java
@@ -24,7 +24,9 @@ import ai.djl.testing.Assertions;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.Base64;
 
 public class BufferedImageFactoryTest {
 
@@ -32,11 +34,16 @@ public class BufferedImageFactoryTest {
     public void testLoadImage() throws IOException {
         try (NDManager manager = NDManager.newBaseManager(TestUtils.getEngine())) {
             ImageFactory factory = ImageFactory.getInstance();
-            Image img =
-                    factory.fromUrl(
-                            "https://github.com/deepjavalibrary/djl/raw/master/examples/src/test/resources/dog_bike_car.jpg");
+            Image img = factory.fromUrl("https://resources.djl.ai/images/dog_bike_car.jpg");
             NDArray array = img.toNDArray(manager);
             Assert.assertEquals(new Shape(img.getHeight(), img.getWidth(), 3), array.getShape());
+
+            try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+                img.save(bos, "png");
+                String data = Base64.getEncoder().encodeToString(bos.toByteArray());
+                Image img2 = factory.fromUrl("data:image/png;base64," + data);
+                Assert.assertEquals(img2.getWidth(), 768);
+            }
         }
     }