diff --git a/contrib/clojure-package/examples/infer/objectdetector/project.clj b/contrib/clojure-package/examples/infer/objectdetector/project.clj index cdd9a8991dc8..da01797f5a21 100644 --- a/contrib/clojure-package/examples/infer/objectdetector/project.clj +++ b/contrib/clojure-package/examples/infer/objectdetector/project.clj @@ -22,7 +22,6 @@ :aliases {"run-detector" ["run" "--" "-m" "models/resnet50_ssd/resnet50_ssd_model" "-i" "images/dog.jpg" "-d" "images/"]} :dependencies [[org.clojure/clojure "1.9.0"] [org.clojure/tools.cli "0.4.1"] - [origami "4.0.0-3"] [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] :main ^:skip-aot infer.objectdetector-example :profiles {:uberjar {:aot :all}}) diff --git a/contrib/clojure-package/examples/infer/objectdetector/src/infer/draw.clj b/contrib/clojure-package/examples/infer/objectdetector/src/infer/draw.clj deleted file mode 100644 index d29b34b5c22a..000000000000 --- a/contrib/clojure-package/examples/infer/objectdetector/src/infer/draw.clj +++ /dev/null @@ -1,44 +0,0 @@ -;; 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 infer.draw - (:require - [opencv4.colors.rgb :as rgb] - [opencv4.core :refer [FONT_HERSHEY_PLAIN imread imwrite new-point put-text! rectangle]])) - -(defn black-boxes! [img results] - (doseq [{confidence :confidence label :label top-left :top-left bottom-right :bottom-right} results] - (let [w (.width img) - h (.height img) - top-left-p (new-point (int (* w (first top-left))) (int (* h (second top-left)))) - bottom-right-p (new-point (int (* w (first bottom-right))) (int (* h (second bottom-right))))] - (if (< 15 confidence) - (do - (rectangle img top-left-p bottom-right-p rgb/white 1) - (put-text! img - (str label "[" confidence "% ]") - top-left-p - FONT_HERSHEY_PLAIN - 1.0 - rgb/white 1))))) - img) - -(defn draw-bounds [image results output-dir] - (let [out-file (str output-dir "/" (.getName (clojure.java.io/as-file image)))] - (-> image - (imread) - (black-boxes! results) - (imwrite out-file)))) \ No newline at end of file diff --git a/contrib/clojure-package/examples/infer/objectdetector/src/infer/objectdetector_example.clj b/contrib/clojure-package/examples/infer/objectdetector/src/infer/objectdetector_example.clj index 9331798b038c..65d822ff36aa 100644 --- a/contrib/clojure-package/examples/infer/objectdetector/src/infer/objectdetector_example.clj +++ b/contrib/clojure-package/examples/infer/objectdetector/src/infer/objectdetector_example.clj @@ -17,13 +17,15 @@ (ns infer.objectdetector-example (:require [org.apache.clojure-mxnet.context :as context] [org.apache.clojure-mxnet.dtype :as dtype] + [org.apache.clojure-mxnet.image :as image] [org.apache.clojure-mxnet.infer :as infer] [org.apache.clojure-mxnet.layout :as layout] [clojure.java.io :as io] - [infer.draw :as draw] - [clojure.string :refer [join]] + [clojure.string :as string] [clojure.tools.cli :refer [parse-opts]]) - (:gen-class)) + (:gen-class) + (:import (javax.imageio ImageIO) + (java.io File))) (defn check-valid-dir "Check that the input directory exists" @@ -54,27 +56,36 @@ :validate [check-valid-dir "Input directory not found"]] ["-h" "--help"]]) -(defn result->map [{:keys [class prob x-min y-min x-max y-max]}] - (hash-map - :label class - :confidence (int (* 100 prob)) - :top-left [x-min y-min] - :bottom-right [x-max y-max])) -(defn print-results [results] - (doseq [_r results] - (println (format "Class: %s Confidence=%s Coords=(%s, %s)" - (_r :label) - (_r :confidence) - (_r :top-left) - (_r :bottom-right))))) +(defn process-result! [output-dir image-path predictions] + (println "looking at image" image-path) + (println "predictions: " predictions) + (let [buf (ImageIO/read (new File image-path)) + width (.getWidth buf) + height (.getHeight buf) + names (mapv :class predictions) + coords (mapv (fn [prediction] + (-> prediction + (update :x-min #(* width %)) + (update :x-max #(* width %)) + (update :y-min #(* height %)) + (update :y-max #(* height %)))) + predictions) + new-img (-> (ImageIO/read (new File image-path)) + (image/draw-bounding-box! coords + {:stroke 2 + :names (mapv #(str (:class %) "-" (:prob %)) + predictions) + :transparency 0.5 + + :font-size-mult 1.0}))] + (->> (string/split image-path #"\/") + last + (io/file output-dir) + (ImageIO/write new-img "jpg")))) (defn process-results [images results output-dir] - (dotimes [i (count images)] - (let [image (nth images i) _results (map result->map (nth results i))] - (println "processing: " image) - (print-results _results) - (draw/draw-bounds image _results output-dir)))) + (doall (map (partial process-result! output-dir) images results))) (defn detect-single-image "Detect objects in a single image and print top-5 predictions" @@ -82,7 +93,7 @@ ([detector input-image output-dir] (.mkdir (io/file output-dir)) (let [image (infer/load-image-from-file input-image) - topk 5 + topk 3 res (infer/detect-objects detector image topk) ] (process-results @@ -109,7 +120,7 @@ (apply concat (for [image-files image-file-batches] (let [image-batch (infer/load-image-paths image-files) - topk 5 + topk 3 res (infer/detect-objects-batch detector image-batch topk) ] (process-results image-files @@ -143,5 +154,5 @@ (parse-opts args cli-options)] (cond (:help options) (println summary) - (some? errors) (println (join "\n" errors)) + (some? errors) (println (string/join "\n" errors)) :else (run-detector options)))) diff --git a/contrib/clojure-package/examples/infer/objectdetector/test/infer/objectdetector_example_test.clj b/contrib/clojure-package/examples/infer/objectdetector/test/infer/objectdetector_example_test.clj index 696d96b3ae3a..3d20c614918f 100644 --- a/contrib/clojure-package/examples/infer/objectdetector/test/infer/objectdetector_example_test.clj +++ b/contrib/clojure-package/examples/infer/objectdetector/test/infer/objectdetector_example_test.clj @@ -47,11 +47,11 @@ {:keys [class prob x-min x-max y-min y-max] :as pred} (first predictions)] (clojure.pprint/pprint predictions) (is (some? predictions)) - (is (= 5 (count predictions))) + (is (= 3 (count predictions))) (is (string? class)) (is (< 0.8 prob)) (is (every? #(< 0 % 1) [x-min x-max y-min y-max])) - (is (= #{"dog" "person" "bicycle" "car"} (set (mapv :class predictions)))))) + (is (= #{"dog" "bicycle" "car"} (set (mapv :class predictions)))))) (deftest test-batch-detection (let [detector (create-detector) @@ -60,7 +60,7 @@ predictions (first batch-predictions) {:keys [class prob x-min x-max y-min y-max] :as pred} (first predictions)] (is (some? batch-predictions)) - (is (= 5 (count predictions))) + (is (= 3 (count predictions))) (is (string? class)) (is (< 0.8 prob)) (println [x-min x-max y-min y-max]) diff --git a/contrib/clojure-package/integration-tests.sh b/contrib/clojure-package/integration-tests.sh index 5ae26e8dfcd4..3df9ba9787b5 100755 --- a/contrib/clojure-package/integration-tests.sh +++ b/contrib/clojure-package/integration-tests.sh @@ -26,7 +26,7 @@ lein install # then run through the examples EXAMPLES_HOME=${MXNET_HOME}/contrib/clojure-package/examples # use AWK pattern for blacklisting -TEST_CASES=`find ${EXAMPLES_HOME} -name test | awk '!/dontselect1|cnn-text-classification|gan|neural-style|infer|pre-trained-models/'` +TEST_CASES=`find ${EXAMPLES_HOME} -name test | awk '!/dontselect1|cnn-text-classification|gan|neural-style|pre-trained-models/'` for i in $TEST_CASES ; do cd ${i} && lein test done 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 e2e98c4e8f01..f81a35803171 100644 --- a/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj +++ b/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj @@ -202,11 +202,11 @@ (Image/toImage input)) (s/def ::buffered-image #(instance? BufferedImage %)) -(s/def ::xmin integer?) -(s/def ::xmax integer?) -(s/def ::ymin integer?) -(s/def ::ymax integer?) -(s/def ::coordinate (s/keys :req-un [::xmin ::xmax ::ymin ::ymax])) +(s/def ::x-min number?) +(s/def ::x-max number?) +(s/def ::y-min number?) +(s/def ::y-max number?) +(s/def ::coordinate (s/keys :req-un [::x-min ::x-max ::y-min ::y-max])) (s/def ::coordinates (s/coll-of ::coordinate)) (s/def ::names (s/nilable (s/coll-of string?))) (s/def ::stroke (s/and integer? pos?)) @@ -217,11 +217,11 @@ (defn- convert-coordinate "Convert bounding box coordinate to Scala correct types." - [{:keys [xmin xmax ymin ymax]}] - {:xmin (int xmin) - :xmax (int xmax) - :ymin (int ymin) - :ymax (int ymax)}) + [{:keys [x-min x-max y-min y-max]}] + {:xmin (int x-min) + :xmax (int x-max) + :ymin (int y-min) + :ymax (int y-max)}) (defn draw-bounding-box! "Draw bounding boxes on `buffered-image` and Mutate the input image. @@ -233,9 +233,9 @@ `transparency`: float in (0.0, 1.0) - Transparency of the bounding box returns: Modified `buffered-image` Ex: - (draw-bounding-box! img [{:xmin 0 :xmax 100 :ymin 0 :ymax 100}]) - (draw-bounding-box! [{:xmin 190 :xmax 850 :ymin 50 :ymax 450} - {:xmin 200 :xmax 350 :ymin 440 :ymax 530}] + (draw-bounding-box! img [{:x-min 0 :x-max 100 :y-min 0 :y-max 100}]) + (draw-bounding-box! [{:x-min 190 :x-max 850 :y-min 50 :y-max 450} + {:x-min 200 :x-max 350 :y-min 440 :y-max 530}] {:stroke 2 :names [\"pug\" \"cookie\"] :transparency 0.8 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 38ab11c86012..23b88d07e896 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 @@ -20,7 +20,8 @@ [org.apache.clojure-mxnet.ndarray :as ndarray] [clojure.java.io :as io] [clojure.test :refer :all]) - (:import (javax.imageio ImageIO))) + (:import (javax.imageio ImageIO) + (java.io File))) (def tmp-dir (System/getProperty "java.io.tmpdir")) (def image-path (.getAbsolutePath (io/file tmp-dir "Pug-Cookie.jpg"))) @@ -76,4 +77,15 @@ (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 (ImageIO/write new-img "png" (io/file tmp-dir "out.png"))))) + +(deftest test-draw-bounding-box! + (let [orig-img (ImageIO/read (new File image-path)) + new-img (-> orig-img + (image/draw-bounding-box! [{:x-min 190 :x-max 850 :y-min 50 :y-max 450} + {:x-min 200 :x-max 350 :y-min 440 :y-max 530}] + {:stroke 2 + :names ["pug" "cookie"] + :transparency 0.8 + :font-size-mult 2.0}))] + (is (ImageIO/write new-img "png" (io/file tmp-dir "out.png"))))) diff --git a/example/quantization/README.md b/example/quantization/README.md index b77537d4fba7..fc9a26755b4e 100644 --- a/example/quantization/README.md +++ b/example/quantization/README.md @@ -35,6 +35,7 @@ The following models have been tested on Linux systems. |[ResNet152-V2](#8)|[MXNet ModelZoo](http://data.mxnet.io/models/imagenet/resnet/152-layers/)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|76.76%/93.03%|76.48%/92.96%| |[Inception-BN](#9)|[MXNet ModelZoo](http://data.mxnet.io/models/imagenet/inception-bn/)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|72.09%/90.60%|72.00%/90.53%| | [SSD-VGG16](#10) | [example/ssd](https://github.com/apache/incubator-mxnet/tree/master/example/ssd) | VOC2007/2012 | 0.8366 mAP | 0.8364 mAP | +| [SSD-VGG16](#10) | [example/ssd](https://github.com/apache/incubator-mxnet/tree/master/example/ssd) | COCO2014 | 0.2552 mAP | 0.253 mAP |
* Note: Caller is responsible to dispose the NDArray * returned by this method after the use. - *
- * @param resizedImage BufferedImage to get pixels from * - * @param inputImageShape Input shape; for example for resnet it is (3,224,224). - Should be same as inputDescriptor shape. - * @param dType The DataType of the NDArray created from the image - * that should be returned. - * Currently it defaults to Dtype.Float32 - * @return NDArray pixels array with shape (3, 224, 224) in CHW format + * @param resizedImage BufferedImage to get pixels from + * @param inputImageShape Input shape; for example for resnet it is (3,224,224). + * Should be same as inputDescriptor shape. + * @param dType The DataType of the NDArray created from the image + * that should be returned. + * Currently it defaults to Dtype.Float32 + * @return NDArray pixels array with shape (3, 224, 224) in CHW format */ def bufferedImageToPixels(resizedImage: BufferedImage, inputImageShape: Shape, dType : DType = DType.Float32): NDArray = { @@ -235,4 +249,4 @@ object ImageClassifier { def loadInputBatch(inputImagePaths: List[String]): Traversable[BufferedImage] = { inputImagePaths.map(path => ImageIO.read(new File(path))) } -} \ No newline at end of file +} diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/MXNetHandler.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/MXNetHandler.scala index d2bed3aa9d80..593bab66bf12 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/MXNetHandler.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/MXNetHandler.scala @@ -23,6 +23,12 @@ import org.slf4j.LoggerFactory private[infer] trait MXNetHandler { + /** + * Executes a function within a thread-safe executor + * @param f The function to execute + * @tparam T The return type of the function + * @return Returns the result of the function f + */ def execute[T](f: => T): T val executor: ExecutorService @@ -31,7 +37,11 @@ private[infer] trait MXNetHandler { private[infer] object MXNetHandlerType extends Enumeration { + /** + * The internal type of the MXNetHandlerType enumeration + */ type MXNetHandlerType = Value + val SingleThreadHandler = Value("MXNetSingleThreadHandler") val OneThreadPerModelHandler = Value("MXNetOneThreadPerModelHandler") } @@ -93,6 +103,10 @@ private[infer] object MXNetSingleThreadHandler extends MXNetThreadPoolHandler(1) private[infer] object MXNetHandler { + /** + * Creates a handler based on the handlerType + * @return A ThreadPool or Thread Handler + */ def apply(): MXNetHandler = { if (handlerType == MXNetHandlerType.OneThreadPerModelHandler) { new MXNetThreadPoolHandler(1) diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ObjectDetector.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ObjectDetector.scala index 28a578cae79f..b78cfbccd987 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ObjectDetector.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ObjectDetector.scala @@ -111,6 +111,13 @@ class ObjectDetector(modelPathPrefix: String, batchResult.toIndexedSeq } + /** + * Formats detection results by sorting in descending order of accuracy (topK only) + * and combining with synset labels + * @param predictResultND The results from the objectDetect call + * @param topK The number of top results to return or None for all + * @return The top predicted results as (className, [Accuracy, Xmin, Ymin, Xmax, Ymax]) + */ private[infer] def sortAndReformat(predictResultND: NDArray, topK: Option[Int]) : IndexedSeq[(String, Array[Float])] = { // iterating over the all the predictions @@ -170,6 +177,18 @@ class ObjectDetector(modelPathPrefix: String, result } + /** + * Creates an image classifier from the object detector model + * @param modelPathPrefix Path prefix from where to load the model artifacts. + * These include the symbol, parameters, and synset.txt. + * Example: file://model-dir/resnet-152 (containing + * resnet-152-symbol.json, resnet-152-0000.params, and synset.txt). + * @param inputDescriptors Descriptors defining the input node names, shape, + * layout and type parameters + * @param contexts Device contexts on which you want to run inference; defaults to CPU + * @param epoch Model epoch to load; defaults to 0 + * @return The corresponding image classifier + */ private[infer] def getImageClassifier(modelPathPrefix: String, inputDescriptors: IndexedSeq[DataDesc], contexts: Array[Context] = Context.cpu(), diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Predictor.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Predictor.scala index 66284c81bd2e..cb27c930903d 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Predictor.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Predictor.scala @@ -33,27 +33,27 @@ import org.slf4j.LoggerFactory private[infer] trait PredictBase { /** - * Converts indexed sequences of 1-D array to NDArrays. - *- * This method will take input as IndexedSeq one dimensional arrays and creates the - * NDArray needed for inference. The array will be reshaped based on the input descriptors. - * @param input: An Indexed Sequence of a one-dimensional array of datatype - * Float or Double - An IndexedSequence is needed when the model has more than one input. - * @return Indexed sequence array of outputs - */ + * Converts indexed sequences of 1-D array to NDArrays. + * This method will take input as IndexedSeq one dimensional arrays and creates the + * NDArray needed for inference. The array will be reshaped based on the input descriptors. + * @tparam T The Scala equivalent of the DType used for the input array and return value + * @param input An Indexed Sequence of a one-dimensional array of datatype + * Float or Double + * An IndexedSequence is needed when the model has more than one input. + * @return Indexed sequence array of outputs + */ def predict[@specialized (Base.MX_PRIMITIVES) T](input: IndexedSeq[Array[T]]) : IndexedSeq[Array[T]] /** - * Predict using NDArray as input. - *
- * This method is useful when the input is a batch of data - * or when multiple operations on the input have to performed. - * Note: User is responsible for managing allocation/deallocation of NDArrays. - * @param input IndexedSequence NDArrays. - * @return Output of predictions as NDArrays. - */ + * Predict using NDArray as input. + *
+ * This method is useful when the input is a batch of data + * or when multiple operations on the input have to performed. + * Note: User is responsible for managing allocation/deallocation of NDArrays. + * @param input IndexedSequence NDArrays. + * @return Output of predictions as NDArrays. + */ def predictWithNDArray(input: IndexedSeq[NDArray]): IndexedSeq[NDArray] /** @@ -248,6 +248,10 @@ class Predictor(modelPathPrefix: String, resultND } + /** + * Creates the module backing the Predictor with the same path, epoch, contexts, and inputs + * @return The Module + */ private[infer] def loadModule(): Module = { val mod = mxNetHandler.execute(Module.loadCheckpoint(modelPathPrefix, epoch.get, contexts = contexts, dataNames = inputDescriptors.map(desc => desc.name))) diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/ObjectDetector.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/ObjectDetector.scala index 05334e49a356..8131273eca94 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/ObjectDetector.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/ObjectDetector.scala @@ -31,19 +31,23 @@ import scala.language.implicitConversions * The ObjectDetector class helps to run ObjectDetection tasks where the goal * is to find bounding boxes and corresponding labels for objects in a image. * - * @param modelPathPrefix Path prefix from where to load the model artifacts. - * These include the symbol, parameters, and synset.txt. - * Example: file://model-dir/ssd_resnet50_512 (containing - * ssd_resnet50_512-symbol.json, ssd_resnet50_512-0000.params, - * and synset.txt) - * @param inputDescriptors Descriptors defining the input node names, shape, - * layout and type parameters - * @param contexts Device contexts on which you want to run inference. - * Defaults to CPU. - * @param epoch Model epoch to load; defaults to 0 + * @param objDetector A source Scala Object detector */ class ObjectDetector private[mxnet] (val objDetector: org.apache.mxnet.infer.ObjectDetector){ + /** + * + * @param modelPathPrefix Path prefix from where to load the model artifacts. + * These include the symbol, parameters, and synset.txt. + * Example: file://model-dir/ssd_resnet50_512 (containing + * ssd_resnet50_512-symbol.json, ssd_resnet50_512-0000.params, + * and synset.txt) + * @param inputDescriptors Descriptors defining the input node names, shape, + * layout and type parameters + * @param contexts Device contexts on which you want to run inference. + * Defaults to CPU. + * @param epoch Model epoch to load; defaults to 0 + */ def this(modelPathPrefix: String, inputDescriptors: java.lang.Iterable[DataDesc], contexts: java.lang.Iterable[Context], epoch: Int) = this { @@ -98,32 +102,78 @@ class ObjectDetector private[mxnet] (val objDetector: org.apache.mxnet.infer.Obj (ret map {a => (a map {e => new ObjectDetectorOutput(e._1, e._2)}).asJava}).asJava } + /** + * Helper to map an implicit conversion + * @param l The value to convert + * @tparam B The desired type + * @tparam A The input type + * @return The converted result + */ def convert[B, A <% B](l: IndexedSeq[A]): IndexedSeq[B] = l map { a => a: B } } object ObjectDetector { - implicit def fromObjectDetector(OD: org.apache.mxnet.infer.ObjectDetector): - ObjectDetector = new ObjectDetector(OD) - - implicit def toObjectDetector(jOD: ObjectDetector): - org.apache.mxnet.infer.ObjectDetector = jOD.objDetector + /** + * Loads an input images from file + * @param inputImagePath Path of single input image + * @return BufferedImage Buffered image + */ def loadImageFromFile(inputImagePath: String): BufferedImage = { org.apache.mxnet.infer.ImageClassifier.loadImageFromFile(inputImagePath) } + /** + * Reshape the input image to a new shape + * + * @param img Input image + * @param newWidth New width for rescaling + * @param newHeight New height for rescaling + * @return Rescaled BufferedImage + */ def reshapeImage(img : BufferedImage, newWidth: Int, newHeight: Int): BufferedImage = { org.apache.mxnet.infer.ImageClassifier.reshapeImage(img, newWidth, newHeight) } + /** + * Convert input BufferedImage to NDArray of input shape + * Note: Caller is responsible to dispose the NDArray + * returned by this method after the use. + * + * @param resizedImage BufferedImage to get pixels from + * @param inputImageShape Input shape; for example for resnet it is (3,224,224). + * Should be same as inputDescriptor shape. + * @return NDArray pixels array with shape (3, 224, 224) in CHW format + */ def bufferedImageToPixels(resizedImage: BufferedImage, inputImageShape: Shape): NDArray = { org.apache.mxnet.infer.ImageClassifier.bufferedImageToPixels(resizedImage, inputImageShape) } + /** + * Loads a batch of images from a folder + * @param inputImagePaths Path to a folder of images + * @return List of buffered images + */ def loadInputBatch(inputImagePaths: java.lang.Iterable[String]): java.util.List[BufferedImage] = { org.apache.mxnet.infer.ImageClassifier .loadInputBatch(inputImagePaths.asScala.toList).toList.asJava } + + /** + * Implicitly convert a Scala ObjectDetector to a Java ObjectDetector + * @param OD The Scala ObjectDetector + * @return The Java ObjectDetector + */ + implicit def fromObjectDetector(OD: org.apache.mxnet.infer.ObjectDetector): + ObjectDetector = new ObjectDetector(OD) + + /** + * Implicitly converts a Java ObjectDetector to a Scala ObjectDetector + * @param jOD The Java ObjectDetector + * @return The Scala ObjectDetector + */ + implicit def toObjectDetector(jOD: ObjectDetector): + org.apache.mxnet.infer.ObjectDetector = jOD.objDetector } diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/Predictor.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/Predictor.scala index 6c0871fae51b..e1505a4da821 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/Predictor.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/Predictor.scala @@ -25,21 +25,25 @@ import scala.collection.JavaConverters._ /** * Implementation of prediction routines. * - * @param modelPathPrefix Path prefix from where to load the model artifacts. - * These include the symbol, parameters, and synset.txt - * Example: file://model-dir/resnet-152 (containing - * resnet-152-symbol.json, resnet-152-0000.params, and synset.txt). - * @param inputDescriptors Descriptors defining the input node names, shape, - * layout and type parameters - *
Note: If the input Descriptors is missing batchSize - * ('N' in layout), a batchSize of 1 is assumed for the model. - * @param contexts Device contexts on which you want to run inference; defaults to CPU - * @param epoch Model epoch to load; defaults to 0 - + * @param predictor The underlying Scala predictor */ // JavaDoc description of class to be updated in https://issues.apache.org/jira/browse/MXNET-1178 class Predictor private[mxnet] (val predictor: org.apache.mxnet.infer.Predictor){ + + /** + * + * @param modelPathPrefix Path prefix from where to load the model artifacts. + * These include the symbol, parameters, and synset.txt + * Example: file://model-dir/resnet-152 (containing + * resnet-152-symbol.json, resnet-152-0000.params, and synset.txt). + * @param inputDescriptors Descriptors defining the input node names, shape, + * layout and type parameters + *
Note: If the input Descriptors is missing batchSize + * ('N' in layout), a batchSize of 1 is assumed for the model. + * @param contexts Device contexts on which you want to run inference; defaults to CPU + * @param epoch Model epoch to load; defaults to 0 + */ def this(modelPathPrefix: String, inputDescriptors: java.lang.Iterable[DataDesc], contexts: java.lang.Iterable[Context], epoch: Int) = this { @@ -53,19 +57,17 @@ class Predictor private[mxnet] (val predictor: org.apache.mxnet.infer.Predictor) * Takes input as Array of one dimensional arrays and creates the NDArray needed for inference * The array will be reshaped based on the input descriptors. Example of calling in Java: * - *
- * {@code + * {{{ * float tmp[][] = new float[1][224]; * for (int x = 0; x < 1; x++) * for (int y = 0; y < 224; y++) * tmp[x][y] = (int)(Math.random()*10); * predictor.predict(tmp); - * } - *+ * }}} * - * @param input: An Array of a one-dimensional array. - An extra Array is needed for when the model has more than one input. - * @return Indexed sequence array of outputs + * @param input An Array of a one-dimensional array. + * An extra Array is needed for when the model has more than one input. + * @return Indexed sequence array of outputs */ def predict(input: Array[Array[Float]]): Array[Array[Float]] = { @@ -76,18 +78,16 @@ class Predictor private[mxnet] (val predictor: org.apache.mxnet.infer.Predictor) * Takes input as Array of one dimensional arrays and creates the NDArray needed for inference * The array will be reshaped based on the input descriptors. Example of calling in Java: * - *
- * {@code + * {{{ * double tmp[][] = new double[1][224]; * for (int x = 0; x < 1; x++) * for (int y = 0; y < 224; y++) * tmp[x][y] = (int)(Math.random()*10); * predictor.predict(tmp); - * } - *+ * }}} * - * @param input: An Array of a one-dimensional array. - An extra Array is needed for when the model has more than one input. + * @param input An Array of a one-dimensional array. + * An extra Array is needed for when the model has more than one input. * @return Indexed sequence array of outputs */ @@ -100,8 +100,8 @@ class Predictor private[mxnet] (val predictor: org.apache.mxnet.infer.Predictor) * Takes input as List of one dimensional iterables and creates the NDArray needed for inference * The array will be reshaped based on the input descriptors. * - * @param input: A List of a one-dimensional iterables of DType Float. - An extra List is needed for when the model has more than one input. + * @param input A List of a one-dimensional iterables of DType Float. + * An extra List is needed for when the model has more than one input. * @return Indexed sequence array of outputs */ def predict(input: java.util.List[java.util.List[java.lang.Float]]): diff --git a/scala-package/macros/pom.xml b/scala-package/macros/pom.xml index 91a13d0fc521..cb84824bb4e2 100644 --- a/scala-package/macros/pom.xml +++ b/scala-package/macros/pom.xml @@ -51,10 +51,6 @@
[[org.apache.mxnet.NDArray]]in spark job + * A wrapper for serialize & deserialize ``org.apache.mxnet.NDArray`` in spark job * @author Yizhi Liu */ class MXNDArray(@transient private var ndArray: NDArray) extends Serializable { diff --git a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNetModel.scala b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNetModel.scala index 2c4c8fe42780..234e9a597cf5 100644 --- a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNetModel.scala +++ b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNetModel.scala @@ -23,7 +23,7 @@ import org.apache.spark.SparkContext import org.apache.spark.mllib.linalg.Vector /** - * Wrapper for
[[org.apache.mxnet.Model]]which used in Spark application + * Wrapper for ``org.apache.mxnet.Model`` which used in Spark application * @author Yizhi Liu */ class MXNetModel private[mxnet]( diff --git a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/utils/Network.scala b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/utils/Network.scala index c61229af0035..836901f69f8f 100644 --- a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/utils/Network.scala +++ b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/utils/Network.scala @@ -20,6 +20,7 @@ package org.apache.mxnet.spark.utils import java.io.IOException import java.net.{ServerSocket, NetworkInterface} import java.util.regex.Pattern +import scala.collection.JavaConverters._ /** * Helper functions to decide ip address / port @@ -33,19 +34,16 @@ object Network { "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$") def ipAddress: String = { - val interfaces = NetworkInterface.getNetworkInterfaces - while (interfaces.hasMoreElements) { - val interface = interfaces.nextElement - val addresses = interface.getInetAddresses - while (addresses.hasMoreElements) { - val address = addresses.nextElement - val ip = address.getHostAddress - if (!ip.startsWith("127.") && IPADDRESS_PATTERN.matcher(ip).matches()) { - return ip + val interfaces = NetworkInterface.getNetworkInterfaces.asScala + val interface = interfaces.toStream.flatMap( + _.getInetAddresses.asScala.toStream.flatMap( + address => { + val ip = address.getHostAddress + Option(ip).filter(ip => !ip.startsWith("127.") && IPADDRESS_PATTERN.matcher(ip).matches()) } - } - } - "127.0.0.1" + ) + ).headOption + interface.getOrElse("127.0.0.1") } def availablePort: Int = { diff --git a/scala-package/spark/src/test/scala/org/apache/mxnet/spark/SharedSparkContext.scala b/scala-package/spark/src/test/scala/org/apache/mxnet/spark/SharedSparkContext.scala index 6d36ca51db90..293cfa13cfce 100644 --- a/scala-package/spark/src/test/scala/org/apache/mxnet/spark/SharedSparkContext.scala +++ b/scala-package/spark/src/test/scala/org/apache/mxnet/spark/SharedSparkContext.scala @@ -92,20 +92,12 @@ trait SharedSparkContext extends FunSuite with BeforeAndAfterEach with BeforeAnd private def getJarFilePath(root: String): String = { val jarFiles = findJars(s"$root/target/") - if (jarFiles != null && jarFiles.nonEmpty) { - jarFiles.head.getAbsolutePath - } else { - null - } + Option(jarFiles).flatMap(_.headOption).map(_.getAbsolutePath).orNull } private def getSparkJar: String = { val jarFiles = findJars(s"$composeWorkingDirPath/target/") - if (jarFiles != null && jarFiles.nonEmpty) { - jarFiles.head.getAbsolutePath - } else { - null - } + Option(jarFiles).flatMap(_.headOption).map(_.getAbsolutePath).orNull } private def getNativeJars(root: String): String = diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index 70ba84b5f94b..45197aafe019 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1401,3 +1401,91 @@ int MXNDArrayCreateFromSharedMem(int shared_pid, int shared_id, const mx_uint *s *out = new NDArray(shared_pid, shared_id, mxnet::TShape(shape, shape + ndim), dtype); API_END(); } + +typedef Engine::VarHandle VarHandle; +typedef Engine::CallbackOnComplete CallbackOnComplete; + +void AssertValidNumberVars(int num_const_vars, int num_mutable_vars) { + CHECK_GE(num_const_vars, 0) << "Non-negative number of const vars expected."; + CHECK_GE(num_mutable_vars, 0) << "Non-negative number of mutable vars expected."; +} + +int MXEnginePushAsync(EngineAsyncFunc async_func, void* func_param, + EngineFuncParamDeleter deleter, ContextHandle ctx_handle, + EngineVarHandle const_vars_handle, int num_const_vars, + EngineVarHandle mutable_vars_handle, int num_mutable_vars, + EngineFnPropertyHandle prop_handle, int priority, + const char* opr_name, bool wait) { + API_BEGIN(); + + auto exec_ctx = *static_cast