Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Port of scala Image API to clojure #13107

Merged
merged 4 commits into from
Nov 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"))))))