Skip to content

Commit

Permalink
Port of scala Image API to clojure (apache#13107)
Browse files Browse the repository at this point in the history
* Port of scala Image API to clojure

* Minor style changes

* Add specs and other minor fixes

* Fix unit tests (:facepalm:)
  • Loading branch information
kedarbellare authored and Jose Luis Contreras committed Nov 13, 2018
1 parent e131c90 commit 5c1542e
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 0 deletions.
139 changes: 139 additions & 0 deletions contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
;;
;; 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.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 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}]
(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)
($/option to-rgb)
($/option output)))
([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 {})))

(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}]
(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)
($/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]
(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 in RGB format to a real image"
[input]
(util/validate! ::to-image-ndarray input "Invalid input array")
(Image/toImage input))
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
;;
;; 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 (.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")
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)
{:color-flag image/GRAYSCALE})]
(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-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 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 cropped-arr)))))

(deftest test-apply-border
(let [img-arr (image/read-image image-path)
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"))))))

0 comments on commit 5c1542e

Please sign in to comment.