From fa679313d1bdf08349afe82fd97c1bb563381422 Mon Sep 17 00:00:00 2001 From: Kedar Bellare 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 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 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 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"))))))