[Decoder] Add bounding box plugin
authorMyungJoo Ham <myungjoo.ham@samsung.com>
Thu, 15 Nov 2018 08:27:40 +0000 (17:27 +0900)
committerjaeyun-jung <39614140+jaeyun-jung@users.noreply.github.com>
Thu, 22 Nov 2018 15:32:31 +0000 (00:32 +0900)
1. The basic mechanism is imported from nnstreamer_example/example_object_detection.
2. The mechanism is updated to support
    - Different tensor types (it supports integers and double as well if the two tensors have the same type)
    - Input tensors may have smaller number of detections (less than DETECTION_MAX)
    - A few performance updates to execute less loops
3. No more dependencies on graphics library for drawing boxes or rendering text
4. Imported "font.c" of Silicon Graphics Inc 1992 to render text. The license is open for any purpose as long as the license statement is in tact.
5. Tested with a basic case
```
gst-launch-1.0 --gst-debug=2  \
        v4l2src name=cam_src ! videoscale ! videoconvert ! video/x-raw,width=640,height=480,format=RGB,framerate=30/1 ! tee name=t \
        t. ! queue leaky=2 max-size-buffers=2 ! videoscale ! video/x-raw,width=300,height=300,format=RGB ! tensor_converter ! \
                tensor_transform mode=arithmetic option=typecast:float32,add:-127,mul:0.007843 ! \
                tensor_filter framework=tensorflow-lite model=ssd_mobilenet_v2_coco.tflite ! \
                tensor_decoder mode=bounding_boxes option1=ssd option2=coco_labels_list.txt option3=box_priors.txt option4=640:480 option5=300:300 ! \
                compositor name=mix sink_0::zorder=2 sink_1::zorder=1 ! videoconvert ! ximagesink \
        t. ! queue leaky=2 max-size-buffers=10 ! mix.
```

In later PRs, we need
- Test cases (and probably some fixes)
- Hardcode the pre-processing results of font.h, then remove the "rasters" from font.h

Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
gst/tensor_decoder/CMakeLists.txt
gst/tensor_decoder/font.h [new file with mode: 0644]
gst/tensor_decoder/tensordec-boundingbox.c [new file with mode: 0644]

index 73ca171..5e13801 100644 (file)
@@ -1,4 +1,5 @@
 ADD_LIBRARY(tensor_decoderOBJ OBJECT tensordec.c tensordec-plugins.c
        tensordec-directvideo.c
        tensordec-imagelabel.c
+       tensordec-boundingbox.c
        )
diff --git a/gst/tensor_decoder/font.h b/gst/tensor_decoder/font.h
new file mode 100644 (file)
index 0000000..9fed530
--- /dev/null
@@ -0,0 +1,153 @@
+/**
+ * (c) Copyright 1993, Silicon Graphics, Inc.
+ * ALL RIGHTS RESERVED
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose and without fee is hereby granted, provided that the above
+ * copyright notice appear in all copies and that both the copyright notice
+ * and this permission notice appear in supporting documentation, and that
+ * the name of Silicon Graphics, Inc. not be used in advertising
+ * or publicity pertaining to distribution of the software without specific,
+ * written prior permission.
+ *
+ * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
+ * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
+ * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
+ * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
+ * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
+ * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
+ * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
+ * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
+ * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
+ * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * US Government Users Restricted Rights
+ * Use, duplication, or disclosure by the Government is subject to
+ * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
+ * (c)(1)(ii) of the Rights in Technical Data and Computer Software
+ * clause at DFARS 252.227-7013 and/or in similar or successor
+ * clauses in the FAR or the DOD or NASA FAR Supplement.
+ * Unpublished-- rights reserved under the copyright laws of the
+ * United States.  Contractor/manufacturer is Silicon Graphics,
+ * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
+ *
+ * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
+ */
+/**
+ * @file font.h
+ * @date 20 Nov 2018
+ * @brief imported from font.c of https://courses.cs.washington.edu/courses/cse457/98a/tech/OpenGL/font.c
+ *        and modified to support tensordec-boundingbox.c
+ * @see https://github.com/nnsuite/nnstreamer
+ * @see https://courses.cs.washington.edu/courses/cse457/98a/tech/OpenGL/font.c
+ * @bug No known bugs.
+ * @author Silicon Graphics Inc. (imported and modified by MyungJoo Ham <myungjoo.ham@samsung.com>)
+ */
+#ifndef _NNSTREAMER_FONT_H_
+#define _NNSTREAMER_FONT_H_
+#include <stdint.h>
+/**
+ * raster[ch][12] is the top pixels
+ * raster[ch][0] is the bottom pixels
+ * raster[ch][height] & 0xF0 is the left-hand side
+ * raster[ch][height] & 0x0F is the right-hand size
+ */
+uint8_t rasters[][13] = {
+{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18},
+{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36},
+{0x00, 0x00, 0x00, 0x66, 0x66, 0xff, 0x66, 0x66, 0xff, 0x66, 0x66, 0x00, 0x00},
+{0x00, 0x00, 0x18, 0x7e, 0xff, 0x1b, 0x1f, 0x7e, 0xf8, 0xd8, 0xff, 0x7e, 0x18},
+{0x00, 0x00, 0x0e, 0x1b, 0xdb, 0x6e, 0x30, 0x18, 0x0c, 0x76, 0xdb, 0xd8, 0x70},
+{0x00, 0x00, 0x7f, 0xc6, 0xcf, 0xd8, 0x70, 0x70, 0xd8, 0xcc, 0xcc, 0x6c, 0x38},
+{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x1c, 0x0c, 0x0e},
+{0x00, 0x00, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c},
+{0x00, 0x00, 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x30},
+{0x00, 0x00, 0x00, 0x00, 0x99, 0x5a, 0x3c, 0xff, 0x3c, 0x5a, 0x99, 0x00, 0x00},
+{0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x00, 0x00},
+{0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x03, 0x03},
+{0x00, 0x00, 0x3c, 0x66, 0xc3, 0xe3, 0xf3, 0xdb, 0xcf, 0xc7, 0xc3, 0x66, 0x3c},
+{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x38, 0x18},
+{0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0xe7, 0x7e},
+{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0x07, 0x03, 0x03, 0xe7, 0x7e},
+{0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xcc, 0x6c, 0x3c, 0x1c, 0x0c},
+{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0xff},
+{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
+{0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x03, 0x03, 0xff},
+{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e},
+{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x03, 0x7f, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e},
+{0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06},
+{0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60},
+{0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x0c, 0x06, 0x03, 0xc3, 0xc3, 0x7e},
+{0x00, 0x00, 0x3f, 0x60, 0xcf, 0xdb, 0xd3, 0xdd, 0xc3, 0x7e, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0x66, 0x3c, 0x18},
+{0x00, 0x00, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
+{0x00, 0x00, 0x7e, 0xe7, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
+{0x00, 0x00, 0xfc, 0xce, 0xc7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0xce, 0xfc},
+{0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xff},
+{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xff},
+{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xcf, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
+{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
+{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e},
+{0x00, 0x00, 0x7c, 0xee, 0xc6, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06},
+{0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xe0, 0xf0, 0xd8, 0xcc, 0xc6, 0xc3},
+{0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0},
+{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xdb, 0xff, 0xff, 0xe7, 0xc3},
+{0x00, 0x00, 0xc7, 0xc7, 0xcf, 0xcf, 0xdf, 0xdb, 0xfb, 0xf3, 0xf3, 0xe3, 0xe3},
+{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0x7e},
+{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
+{0x00, 0x00, 0x3f, 0x6e, 0xdf, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x66, 0x3c},
+{0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
+{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0xe0, 0xc0, 0xc0, 0xe7, 0x7e},
+{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff},
+{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
+{0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
+{0x00, 0x00, 0xc3, 0xe7, 0xff, 0xff, 0xdb, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
+{0x00, 0x00, 0xc3, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3},
+{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3},
+{0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x7e, 0x0c, 0x06, 0x03, 0x03, 0xff},
+{0x00, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c},
+{0x00, 0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60},
+{0x00, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c},
+{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18},
+{0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x30, 0x70},
+{0x00, 0x00, 0x7f, 0xc3, 0xc3, 0x7f, 0x03, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0},
+{0x00, 0x00, 0x7e, 0xc3, 0xc0, 0xc0, 0xc0, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x7f, 0xc3, 0xc3, 0xc3, 0xc3, 0x7f, 0x03, 0x03, 0x03, 0x03, 0x03},
+{0x00, 0x00, 0x7f, 0xc0, 0xc0, 0xfe, 0xc3, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x33, 0x1e},
+{0x7e, 0xc3, 0x03, 0x03, 0x7f, 0xc3, 0xc3, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0},
+{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x00},
+{0x38, 0x6c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x00},
+{0x00, 0x00, 0xc6, 0xcc, 0xf8, 0xf0, 0xd8, 0xcc, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0},
+{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78},
+{0x00, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xfe, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00},
+{0xc0, 0xc0, 0xc0, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0x00, 0x00, 0x00, 0x00},
+{0x03, 0x03, 0x03, 0x7f, 0xc3, 0xc3, 0xc3, 0xc3, 0x7f, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xfe, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0xfe, 0x03, 0x03, 0x7e, 0xc0, 0xc0, 0x7f, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x1c, 0x36, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x00},
+{0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0xc3, 0xe7, 0xff, 0xdb, 0xc3, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0xc3, 0x00, 0x00, 0x00, 0x00},
+{0xc0, 0x60, 0x60, 0x30, 0x18, 0x3c, 0x66, 0x66, 0xc3, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0xff, 0x00, 0x00, 0x00, 0x00},
+{0x00, 0x00, 0x0f, 0x18, 0x18, 0x18, 0x38, 0xf0, 0x38, 0x18, 0x18, 0x18, 0x0f},
+{0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18},
+{0x00, 0x00, 0xf0, 0x18, 0x18, 0x18, 0x1c, 0x0f, 0x1c, 0x18, 0x18, 0x18, 0xf0},
+{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x8f, 0xf1, 0x60, 0x00, 0x00, 0x00}
+};
+#endif /* _NNSTREAMER_FONT_H_ */
diff --git a/gst/tensor_decoder/tensordec-boundingbox.c b/gst/tensor_decoder/tensordec-boundingbox.c
new file mode 100644 (file)
index 0000000..180bc52
--- /dev/null
@@ -0,0 +1,879 @@
+/**
+ * GStreamer / NNStreamer tensor_decoder subplugin, "image labeling"
+ * Copyright (C) 2018 Samsung Electronics Co. Ltd.
+ * Copyright (C) 2018 MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ */
+/**
+ * @file        tensordec-boundingbox.c
+ * @date        15 Nov 2018
+ * @brief       NNStreamer tensor-decoder subplugin, "bounding boxes",
+ *              which converts tensors to video stream w/ boxes on
+ *              transparent background.
+ *              This code is NYI/WIP and not compilable.
+ *
+ * @see         https://github.com/nnsuite/nnstreamer
+ * @author      MyungJoo Ham <myungjoo.ham@samsung.com>
+ * @bug         No known bugs except for NYI items
+ *
+ * option1: Decoder mode of bounding box.
+ *          Available: ssd (single shot multibox detector with priors.)
+ *          Default (unsupplied): ssd
+ * option2: Location of label file
+ *          This is independent from option1
+ * option3: Location of box prior file (ssd) or any option1-dependent values
+ *          !!This depends on option1 values!!
+ * option4: Video Output Dimension (WIDTH:HEIGHT)
+ *          This is independent from option1
+ * option5: Input Dimension (WIDTH:HEIGHT)
+ *          This is independent from option1
+ * option6: Box Style (NYI)
+ *
+ * MAJOR TODO: Support other colorspaces natively from _decode for performance gain
+ * (e.g., BGRA, ARGB, ...)
+ *
+ */
+
+/** @todo getline requires _GNU_SOURCE. remove this later. */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <glib.h>
+#include <gst/gst.h>
+#include <math.h>               /* expf */
+#include <tensor_common.h>
+#include "tensordec.h"
+#include "font.h"
+
+#define BOX_SIZE        4
+#define DETECTION_MAX   1917
+#define PIXEL_VALUE (0xFF0000FF)        /* RED 100% in RGBA */
+
+/**
+ * @todo Fill in the value at build time or hardcode this. It's const value
+ * @brief The bitmap of characters
+ * [Character (ASCII)][Height][Width]
+ */
+static uint32_t singleLineSprite[256][13][8];
+
+/**
+ * @brief There can be different schemes for bounding boxes.
+ */
+typedef enum
+{
+  BOUNDING_BOX_DEFAULT = 0,     /* SSD is the Default */
+  BOUNDING_BOX_SSD = 0,
+  BOUNDING_BOX_UNKNOWN,
+} bounding_box_modes;
+
+/**
+ * @brief List of bounding-box decoding schemes in string
+ */
+static const gchar *bb_modes[] = {
+  [BOUNDING_BOX_SSD] = "ssd",
+  NULL,
+};
+
+/**
+ * @brief Data structure for SSD boundig box info.
+ */
+typedef struct
+{
+  /* From option3, box prior data */
+  gchar *box_prior_path; /**< Box Prior file path */
+  gfloat box_priors[BOX_SIZE][DETECTION_MAX + 1]; /** loaded box prior */
+} properties_SSD;
+
+/**
+ * @brief Data structure for boundig box info.
+ */
+typedef struct
+{
+  bounding_box_modes mode; /**< The bounding box decoding mode */
+
+  union
+  {
+    properties_SSD ssd; /**< Properties for ssd configured by option 1 + 3 */
+  };
+
+  /* From option2 */
+  gchar *label_path; /**< Label file path. */
+  gchar **labels; /**< The list of loaded labels. Null if not loaded */
+  guint total_labels; /**< The number of loaded labels */
+  guint max_word_length; /**< The max size of labels */
+
+  /* From option4 */
+  guint width; /**< Output Video Width */
+  guint height; /**< Output Video Height */
+
+  /* From option5 */
+  guint i_width; /**< Input Video Width */
+  guint i_height; /**< Input Video Height */
+
+  guint max_detection;
+} bounding_boxes;
+
+/** @brief Initialize bounding_boxes per mode */
+static gboolean
+_init_modes (bounding_boxes * bdata)
+{
+  if (bdata->mode == BOUNDING_BOX_SSD) {
+    /* properties_SSD *data = &bdata->ssd; */
+    return TRUE;
+  }
+  return TRUE;
+}
+
+/** @brief tensordec-plugin's TensorDecDef callback */
+static gboolean
+_init (GstTensorDec * self)
+{
+  /** @todo check if we need to ensure plugin_data is not yet allocated */
+  bounding_boxes *bdata;
+  int i, j, k;
+  self->plugin_data = g_new0 (bounding_boxes, 1);
+
+  bdata = self->plugin_data;
+  bdata->mode = BOUNDING_BOX_DEFAULT;
+  bdata->width = 640;
+  bdata->height = 480;
+  bdata->i_width = 300;
+  bdata->i_height = 300;
+
+  /** @todo Constify singleLineSprite and remove this loop */
+  for (i = 0; i < 256; i++) {
+    int ch = i;
+    uint8_t val;
+
+    if (ch < 32 || ch >= 127) {
+      /* It's not ASCII */
+      ch = '*';
+    }
+    ch -= 32;
+
+    for (j = 0; j < 13; j++) {
+      val = rasters[ch][j];
+      for (k = 0; k < 8; k++) {
+        if (val & 0x80)
+          singleLineSprite[i][12 - j][k] = PIXEL_VALUE;
+        else
+          singleLineSprite[i][12 - j][k] = 0;
+        val <<= 1;
+      }
+    }
+  }
+
+  /* The default values when the user didn't specify */
+  return _init_modes (bdata);
+}
+
+/** @brief Free bounding_boxes per mode */
+static void
+_exit_modes (bounding_boxes * bdata)
+{
+  if (bdata->mode == BOUNDING_BOX_SSD) {
+    /* properties_SSD *data = &bdata->ssd; */
+  }
+}
+
+/** @brief tensordec-plugin's TensorDecDef callback */
+static void
+_exit (GstTensorDec * self)
+{
+  bounding_boxes *bdata = self->plugin_data;
+
+  if (bdata->labels) {
+    int i;
+    for (i = 0; i < bdata->total_labels; i++)
+      g_free (bdata->labels[i]);
+    g_free (bdata->labels);
+  }
+  if (bdata->label_path)
+    g_free (bdata->label_path);
+  _exit_modes (bdata);
+
+  g_free (self->plugin_data);
+  self->plugin_data = NULL;
+}
+
+/**
+ * @brief Load label file into the internal data
+ * @param[in/out] data The given ImageLabelData struct.
+ */
+static void
+loadImageLabels (bounding_boxes * data)
+{
+  FILE *fp;
+  int i;
+
+  /* Clean up previously configured data first */
+  if (data->labels) {
+    for (i = 0; i < data->total_labels; i++)
+      g_free (data->labels[i]);
+    g_free (data->labels);
+  }
+  data->labels = NULL;
+  data->total_labels = 0;
+  data->max_word_length = 0;
+
+  if ((fp = fopen (data->label_path, "r")) != NULL) {
+    char *line = NULL;
+    size_t len = 0;
+    ssize_t read;
+    gchar *label;
+
+    GList *labels = NULL, *cursor;
+
+    while ((read = getline (&line, &len, fp)) != -1) {
+      if (line) {
+        if (line[strlen (line) - 1] == '\n') {
+          line[strlen (line) - 1] = '\0';
+        }
+        label = g_strdup ((gchar *) line);
+        labels = g_list_append (labels, label);
+        free (line);
+        if (strlen (label) > data->max_word_length)
+          data->max_word_length = strlen (label);
+      }
+      line = NULL;
+      len = 0;
+    }
+
+    if (line) {
+      free (line);
+    }
+
+    /* Flatten labels (GList) into data->labels (array gchar **) */
+    data->total_labels = g_list_length (labels);
+    data->labels = g_new (gchar *, data->total_labels);
+    i = 0;
+    cursor = g_list_first (labels);
+    for (cursor = labels; cursor != NULL; cursor = cursor->next) {
+      data->labels[i] = cursor->data;
+      i++;
+      g_assert (i <= data->total_labels);
+    }
+    g_list_free (labels);       /* Do not free its elements */
+
+    fclose (fp);
+  } else {
+    GST_ERROR ("Cannot load label file %s", data->label_path);
+    return;
+  }
+
+  GST_INFO ("Loaded image label file successfully. %u labels loaded.",
+      data->total_labels);
+  return;
+}
+
+static gboolean _ssd_loadBoxPrior (bounding_boxes * bdata);
+
+/** @brief configure per-mode option (option3) */
+static gboolean
+_setOption_mode (bounding_boxes * bdata, const gchar * param)
+{
+  if (bdata->mode == BOUNDING_BOX_SSD) {
+    /* Load prior boxes with the path from option3 */
+    properties_SSD *ssd = &bdata->ssd;
+
+    if (ssd->box_prior_path)
+      g_free (ssd->box_prior_path);
+    ssd->box_prior_path = g_strdup (param);
+
+    if (NULL != ssd->box_prior_path)
+      return _ssd_loadBoxPrior (bdata);
+
+  }
+  return TRUE;
+}
+
+/** @brief tensordec-plugin's TensorDecDef callback */
+static gboolean
+_setOption (GstTensorDec * self, int opNum, const gchar * param)
+{
+  bounding_boxes *bdata = self->plugin_data;
+
+  if (opNum == 0) {
+    /* option1 = Bounding Box Decoding mode */
+    bounding_box_modes previous = bdata->mode;
+    bdata->mode = find_key_strv (bb_modes, param);
+
+    if (NULL == param || *param == '\0' || bdata->mode < 0)
+      bdata->mode = BOUNDING_BOX_DEFAULT;
+
+    if (bdata->mode != previous && bdata->mode != BOUNDING_BOX_UNKNOWN) {
+      return _init_modes (bdata);
+    }
+    return TRUE;
+
+  } else if (opNum == 1) {
+    /* option2 = label text file location */
+    if (NULL != bdata->label_path)
+      g_free (bdata->label_path);
+    bdata->label_path = g_strdup (param);
+
+    if (NULL != bdata->label_path)
+      loadImageLabels (bdata);
+
+    if (bdata->total_labels > 0)
+      return TRUE;
+    else
+      return FALSE;
+      /** @todo Do not die for this */
+  } else if (opNum == 2) {
+    /* option3 = per-decoding-mode option */
+    return _setOption_mode (bdata, param);
+  } else if (opNum == 3) {
+    /* option4 = output video size (width:height) */
+    tensor_dim dim;
+    int rank = get_tensor_dimension (param, dim);
+
+    bdata->width = 0;
+    bdata->height = 0;
+    if (param == NULL || *param == '\0')
+      return TRUE;
+
+    if (rank < 2) {
+      GST_ERROR
+          ("mode-option-2 of boundingbox is video output dimension (WIDTH:HEIGHT). The given parameter, \"%s\", is not acceptable.",
+          param);
+      return TRUE;              /* Ignore this param */
+    }
+    if (rank > 2) {
+      GST_WARNING
+          ("mode-option-2 of boundingbox is video output dimension (WIDTH:HEIGHT). The third and later elements of the given parameter, \"%s\", are ignored.",
+          param);
+    }
+    bdata->width = dim[0];
+    bdata->height = dim[1];
+    return TRUE;
+  } else if (opNum == 4) {
+    /* option5 = input model size (width:height) */
+    tensor_dim dim;
+    int rank = get_tensor_dimension (param, dim);
+
+    bdata->i_width = 0;
+    bdata->i_height = 0;
+    if (param == NULL || *param == '\0')
+      return TRUE;
+
+    if (rank < 2) {
+      GST_ERROR
+          ("mode-option-3 of boundingbox is input video dimension (WIDTH:HEIGHT). The given parameter, \"%s\", is not acceptable.",
+          param);
+      return TRUE;              /* Ignore this param */
+    }
+    if (rank > 2) {
+      GST_WARNING
+          ("mode-option-3 of boundingbox is input video dimension (WIDTH:HEIGHT). The third and later elements of the given parameter, \"%s\", are ignored.",
+          param);
+    }
+    bdata->i_width = dim[0];
+    bdata->i_height = dim[1];
+    return TRUE;
+  }
+  /**
+   * @todo Accept color / border-width / ... with option-2
+   */
+  GST_INFO ("Property mode-option-%d is ignored", opNum + 1);
+  return TRUE;
+}
+
+/**
+ * @brief tensordec-plugin's TensorDecDef callback
+ *
+ * The first tensor is boxes. BOX_SIZE : 1 : #MaxDetection, ANY-TYPE
+ * The second tensor is labels. #MaxLabel : #MaxDetection, ANY-TYPE
+ * Both tensors are MANDATORY!
+ *
+ * If there are third or more tensors, such tensors will be ignored.
+ */
+static GstCaps *
+_getOutputDim (GstTensorDec * self, const GstTensorsConfig * config)
+{
+  /** @todo this is compatible with "SSD" only. expand the capability! */
+  bounding_boxes *data = self->plugin_data;
+  GstCaps *caps;
+  const uint32_t *dim1, *dim2;
+  int i;
+  gchar *str;
+  guint max_detection, max_label;
+
+  g_return_val_if_fail (config != NULL, NULL);
+  g_return_val_if_fail (config->info.num_tensors >= 2, NULL);
+  if (config->info.num_tensors > 2) {
+    GST_WARNING ("tensor-decoder:boundingbox accepts 1 or 2 tensors. "
+        "You are wasting the bandwidth by supplying %d tensors.",
+        config->info.num_tensors);
+  }
+
+  /* Check if the first tensor is compatible */
+  dim1 = config->info.info[0].dimension;
+  g_return_val_if_fail (dim1[0] == BOX_SIZE, NULL);
+  g_return_val_if_fail (dim1[1] == 1, NULL);
+  max_detection = dim1[2];
+  g_return_val_if_fail (max_detection > 0, NULL);
+  for (i = 3; i < NNS_TENSOR_RANK_LIMIT; i++)
+    g_return_val_if_fail (dim1[i] == 1, NULL);
+
+  /* Check if the second tensor is compatible */
+  dim2 = config->info.info[1].dimension;
+  max_label = dim2[0];
+  g_return_val_if_fail (max_label <= data->total_labels, NULL);
+  if (max_label < data->total_labels)
+    GST_WARNING
+        ("The given tensor (2nd) has max_label (first dimension: %u) smaller than the number of labels in labels file (%s: %u).",
+        max_label, data->label_path, data->total_labels);
+  g_return_val_if_fail (max_detection == dim2[1], NULL);
+  for (i = 2; i < NNS_TENSOR_RANK_LIMIT; i++)
+    g_return_val_if_fail (dim2[i] == 1, NULL);
+
+  /* tensor-type of the two tensors shoule be the same */
+  g_return_val_if_fail (config->info.info[0].type == config->info.info[1].type,
+      NULL);
+
+  /* Check consistency with max_detection */
+  if (data->max_detection == 0)
+    data->max_detection = max_detection;
+  else
+    g_return_val_if_fail (max_detection == data->max_detection, NULL);
+  if (data->max_detection > DETECTION_MAX) {
+    GST_ERROR
+        ("Incoming tensor has too large detection-max (3rd rank in tensor_0 and 2nd rank in tensor_1) : %u",
+        max_detection);
+    return NULL;
+  }
+
+  str = g_strdup_printf ("video/x-raw, format = RGBA, " /* Use alpha channel to make the background transparent */
+      "width = %u, height = %u"
+      /** @todo Configure framerate! */
+      , data->width, data->height);
+  caps = gst_caps_from_string (str);
+  g_free (str);
+
+  return caps;
+}
+
+/**
+ * @brief Load box-prior data from a file
+ * @param[in/out] bdata The internal data.
+ * @return TRUE if loaded and configured. FALSE if failed to do so.
+ */
+static gboolean
+_ssd_loadBoxPrior (bounding_boxes * bdata)
+{
+  FILE *fp;
+  properties_SSD *ssd = &bdata->ssd;
+
+  if ((fp = fopen (ssd->box_prior_path, "r")) != NULL) {
+    int row;
+    int prev_reg = -1;
+
+    for (row = 0; row < BOX_SIZE; row++) {
+      int column = 0, registered = 0;
+      size_t len = 0;
+      char *line = NULL;
+      ssize_t read;
+
+      read = getline (&line, &len, fp);
+
+      if (read == -1) {
+        GST_ERROR
+            ("Decoder/Bound-Box/SSD's box prior data file has rows too short");
+        return FALSE;
+      }
+      if (line) {
+        gchar **list = g_strsplit_set (line, " \t,", -1);
+        gchar *word;
+
+        while ((word = list[column]) != NULL) {
+          column++;
+
+          if (word && *word) {
+            if (registered > DETECTION_MAX) {
+              GST_WARNING
+                  ("Decoder/Bound-Box/SSD's box prior data file has too many priors. %d >= %d",
+                  registered, DETECTION_MAX);
+              break;
+            }
+            ssd->box_priors[row][registered] = atof (word);
+            registered++;
+          }
+        }
+        g_strfreev (list);
+        free (line);
+      }
+      if (prev_reg != -1 && prev_reg != registered) {
+        GST_ERROR
+            ("Decoder/Bound-Box/SSD's box prior data file is not consistent.");
+        return FALSE;
+      }
+      prev_reg = registered;
+    }
+
+    fclose (fp);
+  } else {
+    GST_ERROR ("Decoder/Bound-Box/SSD's box prior file cannot be read: %s",
+        ssd->box_prior_path);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/** @brief tensordec-plugin's TensorDecDef callback */
+static gsize
+_getTransformSize (GstTensorDec * self, GstCaps * caps,
+    gsize size, GstCaps * othercaps, GstPadDirection direction)
+{
+  return 0;
+  /** @todo Use appropriate values */
+}
+
+/** @brief Represents a detect object */
+typedef struct
+{
+  gboolean valid;
+  int class_id;
+  int x;
+  int y;
+  int width;
+  int height;
+  gfloat prob;
+} detectedObject;
+
+#define DETECTION_THRESHOLD (.5f)
+#define Y_SCALE (10.0f)
+#define X_SCALE (10.0f)
+#define H_SCALE (5.0f)
+#define W_SCALE (5.0f)
+
+#define _expit(x) \
+    (1.f / (1.f + expf (- ((float)x))))
+
+/**
+ * @brief C++-Template-like box location calculation for box-priors
+ * @bug This is not macro-argument safe. Use paranthesis!
+ * @param[in] bb The configuration, "bounding_boxes"
+ * @param[in] index The index (3rd dimension of BOX_SIZE:1:DETECTION_MAX:1)
+ * @param[in] boxprior The box prior data from the box file of SSD.
+ * @param[in] boxinputptr Cursor pointer of input + byte-per-index * index (box)
+ * @param[in] detinputptr Cursor pointer of input + byte-per-index * index (detection)
+ * @param[in] result The object returned. (pointer to object)
+ */
+#define _get_object_i(bb, index, boxprior, boxinputptr, detinputptr, result) \
+  do { \
+    int c; \
+    for (c = 1; c < bb->total_labels; c++) { \
+      gfloat score = _expit (detinputptr[c]); \
+      if (score >= DETECTION_THRESHOLD) { \
+        float ycenter = boxinputptr[0] / Y_SCALE * boxprior[2][index] + boxprior[0][index]; \
+        float xcenter = boxinputptr[1] / X_SCALE * boxprior[3][index] + boxprior[1][index]; \
+        float h = (float) expf (boxinputptr[2] / H_SCALE) * boxprior[2][index]; \
+        float w = (float) expf (boxinputptr[3] / W_SCALE) * boxprior[3][index]; \
+        float ymin = ycenter - h / 2.f; \
+        float xmin = xcenter - w / 2.f; \
+        int x = xmin * bb->i_width; \
+        int y = ymin * bb->i_height; \
+        int width = w * bb->i_width; \
+        int height = h * bb->i_height; \
+        result->class_id = c; \
+        result->x = MAX (0, x); \
+        result->y = MAX (0, y); \
+        result->width = width; \
+        result->height = height; \
+        result->prob = score; \
+        result->valid = TRUE; \
+        break; \
+      } \
+    } \
+   } while (0);
+
+/**
+ * @brief C++-Template-like box location calculation for box-priors
+ * @param[in] bb The configuration, "bounding_boxes"
+ * @param[in] type The tensor type of inputptr
+ * @param[in] typename nnstreamer enum corresponding to the type
+ * @param[in] index The index (3rd dimension of BOX_SIZE:1:DETECTION_MAX:1)
+ * @param[in] boxprior The box prior data from the box file of SSD.
+ * @param[in] boxinput Input Tensor Data (Boxes)
+ * @param[in] detinput Input Tensor Data (Detection). Null if not available. (numtensor ==1)
+ * @param[in] config Tensor configs of the input tensors
+ * @param[out] results The object returned. (GArray with detectedObject)
+ */
+#define _get_objects(bb, _type, typename, boxprior, boxinput, detinput, config, results) \
+  case typename: \
+  { \
+    int d; \
+    _type * boxinput_ = (_type *) boxinput; \
+    gsize boxbpi = config->info.info[0].dimension[0]; \
+    _type * detinput_ = (_type *) detinput; \
+    gsize detbpi = config->info.info[1].dimension[0]; \
+    int num = (DETECTION_MAX > bb->max_detection) ? bb->max_detection : DETECTION_MAX; \
+    detectedObject object; \
+    for (d = 0; d < num; d++) { \
+      _get_object_i (bb, d, boxprior, (boxinput_ + (d * boxbpi)), (detinput_ + (d * detbpi)), (&object)); \
+      if (object.valid == TRUE) { \
+        g_array_append_val (results, object); \
+      } \
+    } \
+  } \
+  break
+
+/** @brief Macro to simplify calling _get_objects */
+#define _get_objects_(type, typename) \
+  _get_objects (bdata, type, typename, (bdata->ssd.box_priors), (boxes->data), (detections->data), config, results)
+
+
+/**
+ * @brief Compare Function for g_array_sort with detectedObject.
+ */
+static gint
+compare_detection (gconstpointer _a, gconstpointer _b)
+{
+  const detectedObject *a = _a;
+  const detectedObject *b = _b;
+
+  /* Larger comes first */
+  return (a->prob > b->prob) ? -1 : ((a->prob == b->prob) ? 0 : 1);
+}
+
+/**
+ * @brief Calculate the intersected surface
+ */
+static gfloat
+iou (detectedObject * a, detectedObject * b)
+{
+  int x1 = MAX (a->x, b->x);
+  int y1 = MAX (a->y, b->y);
+  int x2 = MIN (a->x + a->width, b->x + b->width);
+  int y2 = MIN (a->y + a->height, b->y + b->height);
+  int w = MAX (0, (x2 - x1 + 1));
+  int h = MAX (0, (y2 - y1 + 1));
+  float inter = w * h;
+  float areaA = a->width * a->height;
+  float areaB = b->width * b->height;
+  float o = inter / (areaA + areaB - inter);
+  return (o >= 0) ? o : 0;
+}
+
+#define THRESHOLD_IOU (.5f)
+/**
+ * @brief Apply NMS to the given results (obejcts[DETECTION_MAX])
+ * @param[in/out] results The results to be filtered with nms
+ */
+static void
+nms (GArray * results)
+{
+  guint boxes_size;
+  guint i, j;
+
+  g_array_sort (results, compare_detection);
+  boxes_size = results->len;
+
+  for (i = 0; i < boxes_size; i++) {
+    detectedObject *a = &g_array_index (results, detectedObject, i);
+    if (a->valid == TRUE) {
+      for (j = i + 1; j < boxes_size; j++) {
+        detectedObject *b = &g_array_index (results, detectedObject, j);
+        if (b->valid == TRUE) {
+          if (iou (a, b) > THRESHOLD_IOU) {
+            b->valid = FALSE;
+          }
+        }
+      }
+    }
+  }
+
+  i = 0;
+  do {
+    detectedObject *a = &g_array_index (results, detectedObject, i);
+    if (a->valid == FALSE)
+      g_array_remove_index (results, i);
+    else
+      i++;
+  } while (i < results->len);
+
+}
+
+/**
+ * @brief Draw with the given results (obejcts[DETECTION_MAX]) to the output buffer
+ * @param[out] out_info The output buffer (RGBA plain)
+ * @param[in] bdata The bouding-box internal data.
+ * @param[in] results The final results to be drawn.
+ */
+static void
+draw (GstMapInfo * out_info, bounding_boxes * bdata, GArray * results)
+{
+  uint32_t *frame = (uint32_t *) out_info->data;        /* Let's draw per pixel (4bytes) */
+  int i;
+
+  for (i = 0; i < results->len; i++) {
+    int x1, x2, y1, y2;         /* Box positions on the output surface */
+    int j;
+    uint32_t *pos1, *pos2;
+    const gchar *label;
+    int label_len;
+    detectedObject *a = &g_array_index (results, detectedObject, i);
+
+    /* 1. Draw Boxes */
+    x1 = MAX (0, (bdata->width * a->x) / bdata->i_width);
+    x2 = MIN (bdata->width - 1,
+        (bdata->width * (a->x + a->width)) / bdata->i_width);
+
+    y1 = MAX (0, (bdata->height * a->y) / bdata->i_height);
+    y2 = MIN (bdata->height - 1,
+        (bdata->height * (a->y + a->height)) / bdata->i_height);
+
+    /* 1-1. Horizontal */
+    pos1 = &frame[y1 * bdata->width + x1];
+    pos2 = &frame[y2 * bdata->width + x1];
+    for (j = x1; j <= x2; j++) {
+      *pos1 = PIXEL_VALUE;
+      *pos2 = PIXEL_VALUE;
+      pos1++;
+      pos2++;
+    }
+
+    /* 1-2. Vertical */
+    pos1 = &frame[(y1 + 1) * bdata->width + x1];
+    pos2 = &frame[(y1 + 1) * bdata->width + x2];
+    for (j = y1 + 1; j < y2; j++) {
+      *pos1 = PIXEL_VALUE;
+      *pos2 = PIXEL_VALUE;
+      pos1 += bdata->width;
+      pos2 += bdata->width;
+    }
+
+    /* 2. Write Labels */
+    g_assert (a->class_id > 0 && a->class_id < bdata->total_labels);
+    label = bdata->labels[a->class_id];
+    label_len = strlen (label);
+    /* x1 is the same: x1 = MAX (0, (bdata->width * a->x) / bdata->i_width); */
+    y1 = MAX (0, (y1 - 14));
+    pos1 = &frame[y1 * bdata->width + x1];
+    for (j = 0; j < label_len; j++) {
+      unsigned int char_index = label[j];
+      if ((x1 + 8) > bdata->width)
+        break;                  /* Stop drawing if it may overfill */
+      pos2 = pos1;
+      for (y2 = 0; y2 < 13; y2++) {
+        /* 13 : character height */
+        for (x2 = 0; x2 < 8; x2++) {
+          /* 8: character width */
+          *(pos2 + x2) = singleLineSprite[char_index][y2][x2];
+        }
+        pos2 += bdata->width;
+      }
+      x1 += 9;
+      pos1 += 9;                /* charater width + 1px */
+    }
+  }
+}
+
+/** @brief tensordec-plugin's TensorDecDef callback */
+static GstFlowReturn
+_decode (GstTensorDec * self, const GstTensorMemory * input, GstBuffer * outbuf)
+{
+  bounding_boxes *bdata = self->plugin_data;
+  const gsize size = bdata->width * bdata->height * 4;  /* RGBA */
+  GstMapInfo out_info;
+  GstMemory *out_mem;
+  const GstTensorsConfig *config = &self->tensor_config;
+
+  const int num_tensors = config->info.num_tensors;
+  const GstTensorMemory *boxes, *detections = NULL;
+  GArray *results =
+      g_array_sized_new (FALSE, TRUE, sizeof (detectedObject), 100);
+  /**
+   * @todo 100 is a heuristic number of objects in a picture frame
+   *       We may have better "heuristics" than this.
+   *       For the sake of performance, don't make it too small.
+   */
+
+  g_assert (outbuf);
+  g_assert (num_tensors >= 2);
+
+  boxes = &input[0];
+  if (num_tensors >= 2)
+    detections = &input[1];
+
+  /* Ensure we have outbuf properly allocated */
+  if (gst_buffer_get_size (outbuf) == 0) {
+    out_mem = gst_allocator_alloc (NULL, size, NULL);
+  } else {
+    if (gst_buffer_get_size (outbuf) < size) {
+      gst_buffer_set_size (outbuf, size);
+    }
+    out_mem = gst_buffer_get_all_memory (outbuf);
+  }
+  g_assert (gst_memory_map (out_mem, &out_info, GST_MAP_WRITE));
+
+  /** reset the buffer with alpha 0 / black */
+  memset (out_info.data, 0, size);
+
+  switch (config->info.info[0].type) {
+      _get_objects_ (uint8_t, _NNS_UINT8);
+      _get_objects_ (int8_t, _NNS_INT8);
+      _get_objects_ (uint16_t, _NNS_UINT16);
+      _get_objects_ (int16_t, _NNS_INT16);
+      _get_objects_ (uint32_t, _NNS_UINT32);
+      _get_objects_ (int32_t, _NNS_INT32);
+      _get_objects_ (uint64_t, _NNS_UINT64);
+      _get_objects_ (int64_t, _NNS_INT64);
+      _get_objects_ (float, _NNS_FLOAT32);
+      _get_objects_ (double, _NNS_FLOAT64);
+    default:
+      g_assert (0);
+  }
+
+  nms (results);
+  draw (&out_info, bdata, results);
+  g_array_free (results, TRUE);
+
+  gst_memory_unmap (out_mem, &out_info);
+
+  if (gst_buffer_get_size (outbuf) == 0)
+    gst_buffer_append_memory (outbuf, out_mem);
+
+  return GST_FLOW_OK;
+}
+
+/** @brief Image Labeling tensordec-plugin TensorDecDef instance */
+static TensorDecDef boundingBox = {
+  .modename = "bounding_boxes",
+  .type = OUTPUT_VIDEO,
+  .init = _init,
+  .exit = _exit,
+  .setOption = _setOption,
+  .getOutputDim = _getOutputDim,
+  .getTransformSize = _getTransformSize,
+  .decode = _decode,
+};
+
+/** @brief Initialize this object for tensordec-plugin */
+__attribute__ ((constructor))
+     void init_bb (void)
+{
+  tensordec_probe (&boundingBox);
+}
+
+/** @brief Destruct this object for tensordec-plugin */
+__attribute__ ((destructor))
+     void fini_bb (void)
+{
+  tensordec_exit (boundingBox.modename);
+}