From fa679313d1bdf08349afe82fd97c1bb563381422 Mon Sep 17 00:00:00 2001
From: Kedar Bellare <kedar.bellare@gmail.com>
Date: Sun, 4 Nov 2018 09:43:24 -0800
Subject: [PATCH 1/4] Port of scala Image API to clojure

---
 .../src/org/apache/clojure_mxnet/image.clj    | 83 +++++++++++++++++++
 .../org/apache/clojure_mxnet/image_test.clj   | 75 +++++++++++++++++
 2 files changed, 158 insertions(+)
 create mode 100644 contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
 create mode 100644 contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj

diff --git a/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj b/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
new file mode 100644
index 000000000000..d247a9922839
--- /dev/null
+++ b/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
@@ -0,0 +1,83 @@
+;;
+;; Licensed to the Apache Software Foundation (ASF) under one or more
+;; contributor license agreements.  See the NOTICE file distributed with
+;; this work for additional information regarding copyright ownership.
+;; The ASF licenses this file to You under the Apache License, Version 2.0
+;; (the "License"); you may not use this file except in compliance with
+;; the License.  You may obtain a copy of the License at
+;;
+;;    http://www.apache.org/licenses/LICENSE-2.0
+;;
+;; Unless required by applicable law or agreed to in writing, software
+;; distributed under the License 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.
+;;
+
+(ns org.apache.clojure-mxnet.image
+  (:require [t6.from-scala.core :refer [$ $$] :as $]
+            [org.apache.clojure-mxnet.util :as util]
+            [clojure.spec.alpha :as s])
+  (:import (org.apache.mxnet Image)))
+
+;; Flags for conversion of images
+(def GRAYSCALE 0)
+(def COLOR 1)
+
+(defn decode-image
+  "Decodes an image from an input stream"
+  ([input-stream {:keys [color-flag to-rgb output]
+                  :or {color-flag COLOR to-rgb true output nil}
+                  :as opts}]
+   (Image/imDecode input-stream color-flag to-rgb ($/option output)))
+  ([input-stream]
+   (decode-image input-stream {})))
+
+(defn read-image
+  "Reads an image file and returns an ndarray"
+  ([filename {:keys [color-flag to-rgb output]
+              :or {color-flag nil to-rgb nil output nil}
+              :as opts}] 
+   (Image/imRead 
+    filename 
+    ($/option color-flag)
+    ($/option to-rgb)
+    ($/option output)))
+  ([filename]
+   (read-image filename {})))
+
+(defn resize-image
+  "Resizes the image array to (width, height)"
+  ([input w h {:keys [interpolation output]
+             :or {interpolation nil output nil}
+             :as opts}]
+   (Image/imResize input w h ($/option interpolation) ($/option output)))
+  ([input w h]
+   (resize-image input w h {})))
+
+(defn apply-border
+  "Pad image border"
+  ([input top bottom left right 
+    {:keys [fill-type value values output]
+     :or {fill-type nil value nil values nil output nil}
+     :as opts}]
+   (Image/copyMakeBorder input top bottom left right
+                         ($/option fill-type)
+                         ($/option value)
+                         ($/option values)
+                         ($/option output)))
+  ([input top bottom left right]
+   (apply-border input top bottom left right {})))
+
+(defn fixed-crop
+  "Return a fixed crop of the image"
+  [input x0 y0 w h]
+  (Image/fixedCrop input x0 y0 w h))
+
+(defn to-image
+  "Convert a NDArray image to a real image"
+  [input]
+  (Image/toImage input))
+
+  
diff --git a/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj b/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj
new file mode 100644
index 000000000000..e256291de304
--- /dev/null
+++ b/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj
@@ -0,0 +1,75 @@
+;;
+;; Licensed to the Apache Software Foundation (ASF) under one or more
+;; contributor license agreements.  See the NOTICE file distributed with
+;; this work for additional information regarding copyright ownership.
+;; The ASF licenses this file to You under the Apache License, Version 2.0
+;; (the "License"); you may not use this file except in compliance with
+;; the License.  You may obtain a copy of the License at
+;;
+;;    http://www.apache.org/licenses/LICENSE-2.0
+;;
+;; Unless required by applicable law or agreed to in writing, software
+;; distributed under the License 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.
+;;
+
+(ns org.apache.clojure-mxnet.image-test
+  (:require [org.apache.clojure-mxnet.image :as image]
+            [org.apache.clojure-mxnet.ndarray :as ndarray]
+            [clojure.java.io :as io]
+            [clojure.test :refer :all])
+  (:import (javax.imageio ImageIO)))
+
+(def tmp-dir (System/getProperty "java.io.tmpdir"))
+(def image-path (str tmp-dir "/Pug-Cookie.jpg"))
+
+(defn download-image []
+  (with-open [in (io/input-stream "https://s3.amazonaws.com/model-server/inputs/Pug-Cookie.jpg")
+              out (io/output-stream (io/file image-path))]
+    (io/copy in out)))
+
+(defn delete-image []
+  (io/delete-file image-path))
+
+(defn with-downloaded-image [f]
+  (download-image)
+  (f)
+  (delete-image))
+
+(use-fixtures :once with-downloaded-image)
+
+(deftest test-decode-image
+  (let [img-arr (image/decode-image 
+                 (io/input-stream image-path))
+        img-arr-2 (image/decode-image 
+                   (io/input-stream image-path)
+                   image/GRAYSCALE)]
+    (is [576 1024 3] (ndarray/shape-vec img-arr))
+    (is [576 1024 3] (ndarray/shape-vec img-arr-2))))
+
+(deftest test-read-image
+  (let [img-arr (image/read-image image-path)]
+    (is [576 1024 3] (ndarray/shape-vec img-arr))))
+
+(deftest test-resize-image
+  (let [img-arr (image/read-image image-path)
+        resized-arr (image/resize-image img-arr 224 224)]
+    (is [224 224 3] (ndarray/shape-vec img-arr))))
+
+(deftest test-crop-image
+  (let [img-arr (image/read-image image-path)
+        cropped-arr (image/fixed-crop img-arr 0 0 224 224)]
+    (is [224 224 3] (ndarray/shape-vec img-arr))))
+
+(deftest test-apply-border
+  (let [img-arr (image/read-image image-path)
+        cropped-arr (image/apply-border img-arr 1 1 1 1)]
+    (is [578 1026 3] (ndarray/shape-vec img-arr))))
+
+(deftest test-to-image
+  (let [img-arr (image/read-image image-path)
+        resized-arr (image/resize-image img-arr 224 224)
+        new-img (image/to-image resized-arr)]
+    (is true (ImageIO/write new-img "png" (io/file tmp-dir "out.png")))))

From 64544721c039d592f33c6ce7a47ac40966ff9860 Mon Sep 17 00:00:00 2001
From: Kedar Bellare <kedar.bellare@gmail.com>
Date: Sun, 4 Nov 2018 09:53:45 -0800
Subject: [PATCH 2/4] Minor style changes

---
 .../clojure-package/src/org/apache/clojure_mxnet/image.clj  | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj b/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
index d247a9922839..3e4a18945323 100644
--- a/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
+++ b/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
@@ -50,8 +50,8 @@
 (defn resize-image
   "Resizes the image array to (width, height)"
   ([input w h {:keys [interpolation output]
-             :or {interpolation nil output nil}
-             :as opts}]
+               :or {interpolation nil output nil}
+               :as opts}]
    (Image/imResize input w h ($/option interpolation) ($/option output)))
   ([input w h]
    (resize-image input w h {})))
@@ -79,5 +79,3 @@
   "Convert a NDArray image to a real image"
   [input]
   (Image/toImage input))
-
-  

From c9d3e72ee8392576a1c06c7f9efa303626fc4d32 Mon Sep 17 00:00:00 2001
From: Kedar Bellare <kedar.bellare@gmail.com>
Date: Sun, 4 Nov 2018 19:09:23 -0800
Subject: [PATCH 3/4] Add specs and other minor fixes

---
 .../src/org/apache/clojure_mxnet/image.clj    | 64 ++++++++++++++++++-
 .../org/apache/clojure_mxnet/image_test.clj   | 12 ++--
 2 files changed, 69 insertions(+), 7 deletions(-)

diff --git a/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj b/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
index 3e4a18945323..6e726eba9da6 100644
--- a/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
+++ b/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
@@ -17,28 +17,51 @@
 
 (ns org.apache.clojure-mxnet.image
   (:require [t6.from-scala.core :refer [$ $$] :as $]
+            [org.apache.clojure-mxnet.dtype :as dtype]
+            [org.apache.clojure-mxnet.ndarray :as ndarray]
             [org.apache.clojure-mxnet.util :as util]
             [clojure.spec.alpha :as s])
-  (:import (org.apache.mxnet Image)))
+  (:import (org.apache.mxnet Image NDArray)
+           (java.io InputStream)))
 
 ;; Flags for conversion of images
 (def GRAYSCALE 0)
 (def COLOR 1)
 
+(s/def ::input-stream #(instance? InputStream %))
+(s/def ::color-flag #{GRAYSCALE COLOR})
+(s/def ::to-rgb boolean?)
+(s/def ::ndarray #(instance? NDArray %))
+(s/def ::output (s/or :empty nil? :ndarray ::ndarray))
+(s/def ::decode-image-opts
+  (s/keys :opt-un [::color-flag ::to-rgb ::output]))
+
 (defn decode-image
   "Decodes an image from an input stream"
   ([input-stream {:keys [color-flag to-rgb output]
                   :or {color-flag COLOR to-rgb true output nil}
                   :as opts}]
+   (util/validate! ::input-stream input-stream "Invalid input stream")
+   (util/validate! ::decode-image-opts opts "Invalid options for decoding")
    (Image/imDecode input-stream color-flag to-rgb ($/option output)))
   ([input-stream]
    (decode-image input-stream {})))
 
+(s/def ::filename string?)
+(s/def ::optional-color-flag
+  (s/or :none nil? :some ::color-flag))
+(s/def ::optional-to-rgb
+  (s/or :none nil? :some ::to-rgb))
+
 (defn read-image
   "Reads an image file and returns an ndarray"
   ([filename {:keys [color-flag to-rgb output]
               :or {color-flag nil to-rgb nil output nil}
-              :as opts}] 
+              :as opts}]
+   (util/validate! ::filename filename "Invalid filename")
+   (util/validate! ::optional-color-flag color-flag "Invalid color flag")
+   (util/validate! ::optional-to-rgb to-rgb "Invalid conversion flag")
+   (util/validate! ::output output "Invalid output")
    (Image/imRead 
     filename 
     ($/option color-flag)
@@ -47,11 +70,19 @@
   ([filename]
    (read-image filename {})))
 
+(s/def ::int int?)
+(s/def ::optional-int (s/or :none nil? :some int?))
+
 (defn resize-image
   "Resizes the image array to (width, height)"
   ([input w h {:keys [interpolation output]
                :or {interpolation nil output nil}
                :as opts}]
+   (util/validate! ::ndarray input "Invalid input array")
+   (util/validate! ::int w "Invalid width")
+   (util/validate! ::int h "Invalid height")
+   (util/validate! ::optional-int interpolation "Invalid interpolation")
+   (util/validate! ::output output "Invalid output")
    (Image/imResize input w h ($/option interpolation) ($/option output)))
   ([input w h]
    (resize-image input w h {})))
@@ -62,6 +93,13 @@
     {:keys [fill-type value values output]
      :or {fill-type nil value nil values nil output nil}
      :as opts}]
+   (util/validate! ::ndarray input "Invalid input array")
+   (util/validate! ::int top "Invalid top margin")
+   (util/validate! ::int bottom "Invalid bottom margin")
+   (util/validate! ::int left "Invalid left margin")
+   (util/validate! ::int right "Invalid right margin")
+   (util/validate! ::optional-int fill-type "Invalid fill type")
+   (util/validate! ::output output "Invalid output")
    (Image/copyMakeBorder input top bottom left right
                          ($/option fill-type)
                          ($/option value)
@@ -73,9 +111,29 @@
 (defn fixed-crop
   "Return a fixed crop of the image"
   [input x0 y0 w h]
+  (util/validate! ::ndarray input "Invalid input array")
+  (util/validate! ::int x0 "Invalid starting x coordinate")
+  (util/validate! ::int y0 "Invalid starting y coordinate")
+  (util/validate! ::int w "Invalid width")
+  (util/validate! ::int h "Invalid height")
   (Image/fixedCrop input x0 y0 w h))
 
+(defn rgb-array?
+  "Returns whether the ndarray is in the RGB format"
+  [input]
+  (util/validate! ::ndarray input "Invalid input array")
+  (let [shape (ndarray/shape-vec input)]
+    (and
+     (= 3 (count shape))
+     (= 3 (shape 2)))))
+
+(s/def ::all-bytes #(= dtype/UINT8 (ndarray/dtype %)))
+(s/def ::rgb-array rgb-array?)
+(s/def ::to-image-ndarray
+  (s/and ::ndarray ::all-bytes ::rgb-array))
+
 (defn to-image
-  "Convert a NDArray image to a real image"
+  "Convert a NDArray image in RGB format to a real image"
   [input]
+  (util/validate! ::to-image-ndarray input "Invalid input array")
   (Image/toImage input))
diff --git a/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj b/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj
index e256291de304..74fbba857ef7 100644
--- a/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj
+++ b/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj
@@ -23,7 +23,7 @@
   (:import (javax.imageio ImageIO)))
 
 (def tmp-dir (System/getProperty "java.io.tmpdir"))
-(def image-path (str tmp-dir "/Pug-Cookie.jpg"))
+(def image-path (.getAbsolutePath (io/file tmp-dir "Pug-Cookie.jpg")))
 
 (defn download-image []
   (with-open [in (io/input-stream "https://s3.amazonaws.com/model-server/inputs/Pug-Cookie.jpg")
@@ -45,13 +45,17 @@
                  (io/input-stream image-path))
         img-arr-2 (image/decode-image 
                    (io/input-stream image-path)
-                   image/GRAYSCALE)]
+                   {:color-flag image/GRAYSCALE})]
     (is [576 1024 3] (ndarray/shape-vec img-arr))
     (is [576 1024 3] (ndarray/shape-vec img-arr-2))))
 
 (deftest test-read-image
-  (let [img-arr (image/read-image image-path)]
-    (is [576 1024 3] (ndarray/shape-vec img-arr))))
+  (let [img-arr (image/read-image image-path)
+        img-arr-2 (image/read-image
+                   image-path
+                   {:color-flag image/GRAYSCALE})]
+    (is [576 1024 3] (ndarray/shape-vec img-arr))
+    (is [576 1024 1] (ndarray/shape-vec img-arr))))
 
 (deftest test-resize-image
   (let [img-arr (image/read-image image-path)

From e58cdbe19c0596272a68f19e72e42c65f2b6c90c Mon Sep 17 00:00:00 2001
From: Kedar Bellare <kedar.bellare@gmail.com>
Date: Mon, 5 Nov 2018 21:12:09 -0800
Subject: [PATCH 4/4] Fix unit tests (:facepalm:)

---
 .../org/apache/clojure_mxnet/image_test.clj    | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj b/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj
index 74fbba857ef7..38ab11c86012 100644
--- a/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj
+++ b/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj
@@ -46,34 +46,34 @@
         img-arr-2 (image/decode-image 
                    (io/input-stream image-path)
                    {:color-flag image/GRAYSCALE})]
-    (is [576 1024 3] (ndarray/shape-vec img-arr))
-    (is [576 1024 3] (ndarray/shape-vec img-arr-2))))
+    (is (= [576 1024 3] (ndarray/shape-vec img-arr)))
+    (is (= [576 1024 1] (ndarray/shape-vec img-arr-2)))))
 
 (deftest test-read-image
   (let [img-arr (image/read-image image-path)
         img-arr-2 (image/read-image
                    image-path
                    {:color-flag image/GRAYSCALE})]
-    (is [576 1024 3] (ndarray/shape-vec img-arr))
-    (is [576 1024 1] (ndarray/shape-vec img-arr))))
+    (is (= [576 1024 3] (ndarray/shape-vec img-arr)))
+    (is (= [576 1024 1] (ndarray/shape-vec img-arr-2)))))
 
 (deftest test-resize-image
   (let [img-arr (image/read-image image-path)
         resized-arr (image/resize-image img-arr 224 224)]
-    (is [224 224 3] (ndarray/shape-vec img-arr))))
+    (is (= [224 224 3] (ndarray/shape-vec resized-arr)))))
 
 (deftest test-crop-image
   (let [img-arr (image/read-image image-path)
         cropped-arr (image/fixed-crop img-arr 0 0 224 224)]
-    (is [224 224 3] (ndarray/shape-vec img-arr))))
+    (is (= [224 224 3] (ndarray/shape-vec cropped-arr)))))
 
 (deftest test-apply-border
   (let [img-arr (image/read-image image-path)
-        cropped-arr (image/apply-border img-arr 1 1 1 1)]
-    (is [578 1026 3] (ndarray/shape-vec img-arr))))
+        padded-arr (image/apply-border img-arr 1 1 1 1)]
+    (is (= [578 1026 3] (ndarray/shape-vec padded-arr)))))
 
 (deftest test-to-image
   (let [img-arr (image/read-image image-path)
         resized-arr (image/resize-image img-arr 224 224)
         new-img (image/to-image resized-arr)]
-    (is true (ImageIO/write new-img "png" (io/file tmp-dir "out.png")))))
+    (is (= true (ImageIO/write new-img "png" (io/file tmp-dir "out.png"))))))
