Repository: deepjavalibrary/djl
Pull request #1839 (2022-07-26 16:57:01)

[api] Fixes NDArrayAdapter toDevice() and toType() behavior
Change-Id: Iaf90c609a832f3f7bc2a863bdb44cfc7df34dbae

## 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/android/core/src/main/java/ai/djl/android/core/BitmapImageFactory.java b/android/core/src/main/java/ai/djl/android/core/BitmapImageFactory.java
index f5e29a6db..aeca1d997 100644
--- a/android/core/src/main/java/ai/djl/android/core/BitmapImageFactory.java
+++ b/android/core/src/main/java/ai/djl/android/core/BitmapImageFactory.java
@@ -90,15 +90,26 @@ public class BitmapImageFactory extends ImageFactory {
         } else if (shape.get(0) == 1 || shape.get(2) == 1) {
             throw new UnsupportedOperationException("Grayscale image is not supported");
         }
+        int[] raw = array.toType(DataType.UINT8, false).toUint8Array();
         if (NDImageUtils.isCHW(shape)) {
-            array = array.transpose(1, 2, 0);
-            shape = array.getShape();
+            int height = (int) shape.get(1);
+            int width = (int) shape.get(2);
+            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            int imageArea = width * height;
+            for (int h = 0; h < height; ++h) {
+                for (int w = 0; w < width; ++w) {
+                    int index = h * width + w;
+                    int red = raw[index] & 0xFF;
+                    int green = raw[imageArea + index] & 0xFF;
+                    int blue = raw[imageArea * 2 + index] & 0xFF;
+                    bitmap.setPixel(w, h, Color.argb(255, red, green, blue));
+                }
+            }
+            return new BitMapWrapper(bitmap);
         }
-
         int height = (int) shape.get(0);
         int width = (int) shape.get(1);
         Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        int[] raw = array.toType(DataType.UINT8, false).toUint8Array();
         for (int h = 0; h < height; ++h) {
             for (int w = 0; w < width; ++w) {
                 int pos = (h * width + w) * 3;
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 c02c36464..a66050657 100644
--- a/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/BufferedImageFactory.java
@@ -90,14 +90,28 @@ public class BufferedImageFactory extends ImageFactory {
         } else if (shape.get(0) == 1 || shape.get(2) == 1) {
             throw new UnsupportedOperationException("Grayscale image is not supported");
         }
+        int[] raw = array.toType(DataType.UINT8, false).toUint8Array();
         if (NDImageUtils.isCHW(shape)) {
-            array = array.transpose(1, 2, 0);
-            shape = array.getShape();
+            int height = (int) shape.get(1);
+            int width = (int) shape.get(2);
+            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+            int[] pixels = new int[width * height];
+            int imageArea = height * width;
+            for (int h = 0; h < height; ++h) {
+                for (int w = 0; w < width; ++w) {
+                    int index = h * width + w;
+                    int red = raw[index];
+                    int green = raw[imageArea + index];
+                    int blue = raw[imageArea * 2 + index];
+                    pixels[index] = (red << 16) | (green << 8) | blue;
+                }
+            }
+            image.setRGB(0, 0, width, height, pixels, 0, width);
+            return new BufferedImageWrapper(image);
         }
         int height = (int) shape.get(0);
         int width = (int) shape.get(1);
         BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
-        int[] raw = array.toType(DataType.UINT8, false).toUint8Array();
         int[] pixels = new int[width * height];
         for (int h = 0; h < height; ++h) {
             for (int w = 0; w < width; ++w) {
diff --git a/api/src/main/java/ai/djl/ndarray/NDArrayAdapter.java b/api/src/main/java/ai/djl/ndarray/NDArrayAdapter.java
index 541e93d75..23b788cb3 100644
--- a/api/src/main/java/ai/djl/ndarray/NDArrayAdapter.java
+++ b/api/src/main/java/ai/djl/ndarray/NDArrayAdapter.java
@@ -133,19 +133,26 @@ public abstract class NDArrayAdapter implements NDArray {
     /** {@inheritDoc} */
     @Override
     public NDArray toDevice(Device device, boolean copy) {
-        if (device.equals(getDevice()) && !copy) {
+        if (device.equals(getDevice())) {
+            if (copy) {
+                return duplicate();
+            }
             return this;
         }
-        return duplicate();
+        throw new UnsupportedOperationException(UNSUPPORTED_MSG);
     }
 
     /** {@inheritDoc} */
     @Override
     public NDArray toType(DataType dataType, boolean copy) {
-        if (dataType.equals(getDataType()) && !copy) {
+        if (dataType.equals(getDataType())) {
+            if (copy) {
+                return duplicate();
+            }
             return this;
         }
-        return duplicate();
+        // TODO: each engine should override this method
+        throw new UnsupportedOperationException(UNSUPPORTED_MSG);
     }
 
     /** {@inheritDoc} */
diff --git a/engines/pytorch/pytorch-engine/README.md b/engines/pytorch/pytorch-engine/README.md
index 6f6bea49b..302ee497d 100644
--- a/engines/pytorch/pytorch-engine/README.md
+++ b/engines/pytorch/pytorch-engine/README.md
@@ -234,7 +234,6 @@ For the Windows platform, you can choose between CPU and GPU.
 
 - ai.djl.pytorch:pytorch-jni:1.11.0-0.18.0
 - ai.djl.pytorch:pytorch-native-cu113:1.11.0:win-x86_64
-- ai.djl.pytorch:pytorch-native-cu102:1.11.0:win-x86_64
 
 ```xml
 <dependency>
@@ -252,22 +251,6 @@ For the Windows platform, you can choose between CPU and GPU.
 </dependency>
 ```
 
-```xml
-<dependency>
-    <groupId>ai.djl.pytorch</groupId>
-    <artifactId>pytorch-native-cu102</artifactId>
-    <classifier>win-x86_64</classifier>
-    <version>1.11.0</version>
-    <scope>runtime</scope>
-</dependency>
-<dependency>
-    <groupId>ai.djl.pytorch</groupId>
-    <artifactId>pytorch-jni</artifactId>
-    <version>1.11.0-0.18.0</version>
-    <scope>runtime</scope>
-</dependency>
-```
-
 ### Windows CPU
 
 - ai.djl.pytorch:pytorch-jni:1.11.0-0.18.0


Test patch:
diff --git a/android/core/src/androidTest/java/ai/djl/android/core/BitmapWrapperTest.java b/android/core/src/androidTest/java/ai/djl/android/core/BitmapWrapperTest.java
index 20d42e038..d54f8dcd9 100644
--- a/android/core/src/androidTest/java/ai/djl/android/core/BitmapWrapperTest.java
+++ b/android/core/src/androidTest/java/ai/djl/android/core/BitmapWrapperTest.java
@@ -90,6 +90,12 @@ public class BitmapWrapperTest {
                     .transpose(2, 0, 1)
                     .toType(DataType.FLOAT32, true);
             assertEquals(array, converted);
+
+            array = manager.arange(0, 12, 1, DataType.UINT8).reshape(3, 2, 2);
+            array = array.transpose(1, 2, 0);
+            image = factory.fromNDArray(array);
+            converted = image.toNDArray(manager);
+            assertEquals(array, converted);
         }
     }
 }
diff --git a/android/core/src/androidTest/java/ai/djl/android/core/ModelLoadingTest.java b/android/core/src/androidTest/java/ai/djl/android/core/ModelLoadingTest.java
index 1ae1b73a9..ea655a6bd 100644
--- a/android/core/src/androidTest/java/ai/djl/android/core/ModelLoadingTest.java
+++ b/android/core/src/androidTest/java/ai/djl/android/core/ModelLoadingTest.java
@@ -49,7 +49,7 @@ public class ModelLoadingTest {
         try (ZooModel<Image, Classifications> model = ModelZoo.loadModel(criteria);
              Predictor<Image, Classifications> predictor = model.newPredictor()) {
             Classifications result = predictor.predict(image);
-            Assert.assertEquals("dog", result.best().getClassName());
+            Assert.assertEquals("matches", result.best().getClassName());
         }
     }
 }
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 a01db157f..f7420c750 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
@@ -47,6 +47,11 @@ public class BufferedImageFactoryTest {
             NDArray converted =
                     image.toNDArray(manager).transpose(2, 0, 1).toType(DataType.FLOAT32, true);
             Assertions.assertAlmostEquals(array, converted);
+
+            array = array.transpose(1, 2, 0);
+            image = factory.fromNDArray(array);
+            converted = image.toNDArray(manager).toType(DataType.FLOAT32, false);
+            Assertions.assertAlmostEquals(array, converted);
         }
     }
 }