update demo page and add object drawing capability 28/2428/1
authorEric Z <ezavesky@research.att.com>
Sun, 29 Jul 2018 13:47:32 +0000 (08:47 -0500)
committerEric Z <ezavesky@research.att.com>
Sun, 29 Jul 2018 13:47:32 +0000 (08:47 -0500)
- update demo page to use standard media ribbon divs (no buttons)
- use mediaList definition instead of inline HTML for programmatic
- move to standard demo CSS

Signed-off-by: Eric Z <ezavesky@research.att.com>
Change-Id: Ib578f1dd1163c2002381292b4e6b3f02221dfcf9

docs/release-notes.rst
docs/tutorials/demonstration.rst
docs/tutorials/example_running_detect.jpg [new file with mode: 0644]
web_demo/demo-framework.css [moved from web_demo/face-privacy.css with 77% similarity]
web_demo/demo-framework.js
web_demo/face-privacy.html
web_demo/face-privacy.js
web_demo/spinnerbar.gif [new file with mode: 0644]

index 4690dc5..f737683 100644 (file)
@@ -26,6 +26,7 @@ Face Privacy Filter Release Notes
 =====
 
 -  Clean up tutorial documentation naming and remove deprecated swagger demo app
+-  Standardize demo CSS, add region drawing to demo page
 
 0.3.3
 =====
index 1904852..8fc405d 100644 (file)
@@ -172,7 +172,19 @@ visualize the results of image classification.
 
 
 .. image:: example_running.jpg
-    :alt: example web application with *awe* mood
+    :alt: example web application with blurring activated
     :width: 200
 
 
+Reuse with object detectors
+---------------------------
+This framework can be used to demonstrate other detector and manipulation models 
+as well.  If the detect model included in this repo is used, faces can be detected
+and illustrated as shown below.  The example below shows use of the
+relevant endpoint and ``.proto`` file (also included in this sample).
+
+.. _demonstration-face-privacy_running_example_obj:
+.. image:: example_running_detect.jpg
+    :alt: example web application detecting faces
+    :width: 200
+
diff --git a/docs/tutorials/example_running_detect.jpg b/docs/tutorials/example_running_detect.jpg
new file mode 100644 (file)
index 0000000..00e76a1
Binary files /dev/null and b/docs/tutorials/example_running_detect.jpg differ
similarity index 77%
rename from web_demo/face-privacy.css
rename to web_demo/demo-framework.css
index 853f93d..e68210a 100644 (file)
@@ -34,18 +34,18 @@ hr {
   z-index: 1;
 }
 .videoWin {
-    //margin: -25px 0px;
-    //height: 80px;
-    // width: 120px;
-    // position: absolute;
-    // left: 15px;
-    // top: 10px;
-    max-width:100%;
-    max-height:300px;
+    margin: 5px;
+    height: 80px;
+    width: 120px;
+    position: absolute;
+    /* max-width:100%;
+    max-height:300px; */
     margin-left: auto;
     margin-right: auto;
-    width: auto;
-    height: auto;
+    /* width: auto;
+    height: auto; */
+    border: solid #888 3px;
+    background: rgba(221, 221, 221, 0.8); // #DDD;
 }
 
 
@@ -128,3 +128,23 @@ br {
     display: block;
 }
 
+.mediaRibbon div .colorblock, .colorblock {
+    height: 5px;
+    width: 5px;
+    border: 1px solid black;
+    background-color: white;
+    display: block;
+    float: left;
+}
+
+#postSpinner {
+    background-image: url('spinnerbar.gif');
+    background-size:     contain;
+    background-repeat:   no-repeat;
+    height: 100px;
+    background-position: center center;
+    border: none;
+    width: 98%;
+    margin-left: auto;
+    margin-right: auto;
+}
\ No newline at end of file
index c5ee130..2997f53 100644 (file)
@@ -30,6 +30,7 @@
  E. Zavesky 05/05/18 adapted for row-based image and other results
  E. Zavesky 05/30/18 adapted for single image input, github preview, safe posting (forked from model-specific code)
  E. Zavesky 07/11/18 allow proto type grouping, proto text file download, binary chunk upload (as proto)
+ E. Zavesky 07/27/18 update for use of video list for auto-population of ribbon with div, not image; add rect drawing function
  */
 
 
@@ -44,7 +45,9 @@ function demo_init(objSetting) {
         classificationServer: getUrlParameter('url-image'), // default to what's in our url prameter
         protoObj: null,   // to be back-filled after protobuf load {'root':obj, 'methods':{'xx':{'typeIn':x, 'typeOut':y}} }
         protoPayloadInput: null,   //payload for encoded message download (if desired)
+        protoInputName: 'in',  //input name for proto creation
         protoPayloadOutput: null,   //payload for encoded message download (if desired)
+        protoOutputName: 'out',  //output name for proto download
         protoKeys: null,  // currently selected protobuf method (if any)
         frameCounter: 0,
         totalFrames: 900000,   // stop after this many frames just to avoid sending frames forever if someone leaves page up
@@ -53,6 +56,8 @@ function demo_init(objSetting) {
         maxSrcVideoWidth: 512, // maximum image width for processing
         serverIsLocal: true,    // server is local versus 'firewall' version
         imageIsWaiting: false,  // blocking to prevent too many queued frames
+        colorSet: [ "#FF0000", "#FFFF00", "#00FF00", "#00FFFF", "#0000FF", "#FF00FF", 
+                    "#FFBFBF", "#FFFFBF", "#BFFFBF", "#BFFFFF", "#BFBFFF", "#FFBFFF"],  // colors for rect and highlight
 
         // functional customizations for each demo
         documentTitle: "Protobuf Demo",
@@ -64,12 +69,23 @@ function demo_init(objSetting) {
         // Objects from DOM elements
         video: document.getElementById('srcVideo'),
         srcImgCanvas: document.getElementById('srcImgCanvas'), // we have a 'src' source image
+        srcImgCanvasBack: null,  // our back-frame to switch canvas
+        srcImgCanvasActive: 0,
     }));
 
     var hd = $(document.body).data('hdparams');
     if (hd.video) {
         hd.video.addEventListener("loadedmetadata", newVideo);
     }
+    //create clone of canvas for better show of data
+    if (hd.srcImgCanvas) {
+        var domCanvas = $(hd.srcImgCanvas);
+        var idCopy = domCanvas.attr('id')+'_clone';
+        domCanvas.clone().attr('id', idCopy).appendTo(domCanvas.parent());
+        hd.srcImgCanvasBack = document.getElementById(idCopy);
+        $(hd.srcImgCanvasBack).hide();        
+    }
+
 
     $("#protoSource").prop("disabled",true).click(downloadBlobProto);
     $("#protoInput").prop("disabled",true).click(downloadBlobIn);
@@ -95,15 +111,38 @@ function demo_init(objSetting) {
         $("#protoSource").prop("disabled",false);
     });
 
-    // add buttons to change video
+    // add div to change video or image source
     $.each(hd.mediaList, function(key) {
-        //TODO: integrarte as DIV instead of button
-        var button = $('<button/>').text(videos[key].name).attr('movie', videos[key].url);
-        $("#sourceRibbon").append(button);
+        //var button = $('<button/>').text(videos[key].name).attr('movie', videos[key].url);
+        var div_area = $('<div/>');
+        var img_dom = $("<img src='"+hd.mediaList[key].img+"' />");
+        if (hd.mediaList[key].movie) {
+            img_dom.attr("movie", hd.mediaList[key].movie);
+        }
+        div_area.append(img_dom);
+        div_area.append($("<span />").append($("<a href='"+hd.mediaList[key].source+"' target='_new' />").text(hd.mediaList[key].name)));
+        $("#sourceRibbon").append(div_area);
+    });
+    
+    //add the file upload capability
+    var div_area = $('<div/>');
+    div_area.append($("<label />").text("Upload Image").append("<br />"));
+    div_area.append($("<input id='imageLoader' name='imageLoader' type='file' />"));
+    $("#sourceRibbon").append(div_area);
+    $("#imageLoader").change(function(e) {
+        clearInterval(hd.frameTimer);  // stop the processing
+        hd.video.pause();
+        $(hd.video).hide();
+        // $(hd.srcImgCanvas).show();
+        var reader = new FileReader();
+        reader.onload = function(event){
+            switchImage(event.target.result);
+        }
+        reader.readAsDataURL(e.target.files[0]);
     });
 
     // add buttons to change video or image
-       $("#sourceRibbon").children("div,button").click(function() {
+       $("#sourceRibbon").children("div").click(function() {
         var $this = $(this);
         $this.siblings().removeClass('selected'); //clear other selection
         $this.addClass('selected');
@@ -123,13 +162,31 @@ function demo_init(objSetting) {
         }
         else {
             $(hd.video).hide();
-            $(srcImgCanvas).show();
-            if (objImg)
+            // $(hd.srcImgCanvas).show();
+            if (objImg) 
                 switchImage(objImg.attr('src'));
         }
        }).first().click();
 }
 
+// trick for two-canvas fetch (essentially using a frame buffer https://en.wikipedia.org/wiki/Framebuffer#Page_flipping)
+function canvas_get(getActive=true) {
+    var hd = $(document.body).data('hdparams');
+    if (getActive)
+        return (hd.srcImgCanvasActive == 0) ? hd.srcImgCanvas : hd.srcImgCanvasBack;
+    return (hd.srcImgCanvasActive != 0) ? hd.srcImgCanvas : hd.srcImgCanvasBack;
+}
+
+// flip display of the two canvases 
+function canvas_flip() {
+    var hd = $(document.body).data('hdparams');
+    var canvasHide = canvas_get(true);
+    var canvasShow = canvas_get(false);
+    hd.srcImgCanvasActive = (hd.srcImgCanvasActive+1) % 2;
+    $(canvasHide).hide();
+    $(canvasShow).show();
+    return canvasShow;
+}
 
 function protobuf_load(pathProto, forceSelect) {
     protobuf.load(pathProto, function(err, root) {
@@ -233,7 +290,7 @@ function switchVideo(movieAttr) {
 
     // Set the video source based on URL specified in the 'videos' list, or select camera input
     $(hd.video).show();
-    $(srcImgCanvas).hide();
+    // $(hd.srcImgCanvas).hide();
     if (movieAttr == "Camera") {
         var constraints = {audio: false, video: true};
         navigator.mediaDevices.getUserMedia(constraints)
@@ -267,9 +324,10 @@ function newVideo() {
        if (pwidth > hd.maxSrcVideoWidth) {
                pwidth = hd.maxSrcVideoWidth;
                pheight = Math.floor((pwidth / hd.video.videoWidth) * pheight); // preserve aspect ratio
-       }
-       hd.srcImgCanvas.width = pwidth;
-       hd.srcImgCanvas.height = pheight;
+    }
+    var canvasAct = canvas_get();
+       canvasAct.width = pwidth;
+       canvasAct.height = pheight;
 
     updateProto("protoMethod");
     hd.frameTimer = setInterval(nextFrame, hd.frameInterval); // start the processing
@@ -288,7 +346,7 @@ function nextFrame() {
 }
 
 function switchImage(imgSrc, isVideo) {
-    var canvas = $(document.body).data('hdparams')['srcImgCanvas'];
+    var canvas = canvas_get(false);
     if (!isVideo) {
         var img = new Image();
         img.crossOrigin = "Anonymous";
@@ -392,6 +450,9 @@ function doPostImage(srcCanvas, dstDiv, dstImg, imgPlaceholder) {
         //  protoc --decode=mMJuVapnmIbrHlZGKyuuPDXsrkzpGqcr.FaceImage model.proto < protobuf.bin
         $("#protoInput").prop("disabled",false);
         hd.protoPayloadInput = sendPayload;
+        // got the input name, from the type, use it here
+        hd.protoInputName = hd.protoObj[hd.protoKeys[0]]['methods'][hd.protoKeys[1]]['typeIn'];
+        hd.protoOutputName = hd.protoObj[hd.protoKeys[0]]['methods'][hd.protoKeys[1]]['typeOut'];
 
         //request.responseType = 'arraybuffer';
     }
@@ -484,6 +545,7 @@ function doPostPayload(sendPayload, domHeaders, dstDiv, dstImg, imgPlaceholder)
             //  dstImg: the dom element of a destination image (if available)
             //  imgPlaceholder: the exported canvas image from last source
             //
+            canvas_flip();
             var returnState = processResult(data, dstDiv, hd.protoKeys, dstImg, imgPlaceholder);
             hd.imageIsWaiting = false;
             return returnState;
@@ -536,11 +598,13 @@ function BlobToDataURI(data, mime) {
 
 // ----- diagnostic tool to download binary blobs ----
 function downloadBlobOut() {
-    return downloadBlob($(document.body).data('hdparams').protoPayloadOutput, "protobuf.out.bin");
+    var hd = $(document.body).data('hdparams');
+    return downloadBlob(hd.protoPayloadOutput, "protobuf."+hd.protoOutputName+".bin");
 }
 
 function downloadBlobIn() {
-    return downloadBlob($(document.body).data('hdparams').protoPayloadInput, "protobuf.in.bin");
+    var hd = $(document.body).data('hdparams');
+    return downloadBlob(hd.protoPayloadInput, "protobuf."+hd.protoInputName+".bin");
 }
 
 function downloadBlobProto() {
@@ -597,3 +661,32 @@ function handleImage(e){
 }
 
 
+// draw a region in the source canvas 
+function canvas_rect(clear_first, r_left, r_top, r_width, r_height, r_color) {
+    if (!r_color) r_color = "blue";
+
+    var line_width = 4;
+    var src_canvas = canvas_get();
+    
+    var hd = $(document.body).data('hdparams');
+    var ctx = src_canvas.getContext('2d');
+    if (clear_first) {
+        ctx.clearRect(0, 0, src_canvas.width, src_canvas.height);
+    }
+
+    //key to starting different colors
+    ctx.beginPath();
+    ctx.lineWidth=line_width;
+    var offsWidth = Math.floor(line_width/2);
+    ctx.strokeStyle=r_color;
+    ctx.moveTo(r_left+offsWidth, r_top+offsWidth);
+    ctx.lineTo(r_left+offsWidth+r_width, r_top+offsWidth);
+    ctx.lineTo(r_left+offsWidth+r_width, r_top+offsWidth+r_height);
+    ctx.lineTo(r_left+offsWidth, r_top+offsWidth+r_height);
+    ctx.lineTo(r_left+offsWidth, r_top+offsWidth);
+    ctx.stroke();
+    //ctx.strokeRect(r_left+line_width, r_top+line_width, r_width, r_height);
+    //console.log("[canvas_rect]: "+r_left+","+r_top+"x"+r_width+","+r_height+", color:"+r_color);
+}
+
+
index 3fcf7d6..ca8db7f 100644 (file)
@@ -28,7 +28,7 @@ E. Zavesky 10/17/17
 Rewrite to utilize simple image-based processing steps.
 -->
 <title>Face Privacy Processing</title>
-<link rel="stylesheet" type="text/css" href="face-privacy.css" />
+<link rel="stylesheet" type="text/css" href="demo-framework.css" />
 </head>
 <body>
 <div class="mediaRibbon">
@@ -37,12 +37,12 @@ Rewrite to utilize simple image-based processing steps.
             <source id="mp4" src="images/commercial.mp4" type="video/mp4"></source>
             Your browser does not support the video tag. Please use Chrome or Firefox.
         </video>
-        <canvas id="srcImgCanvas" width="380" height="270" ></canvas>
         <span>raw image</span>
+        <canvas id="srcImgCanvas" width="380" height="270" crossorigin></canvas>
     </div>
     <div>
-        <img id="destImg" width="380" height="270" crossorigin />
         <span>post-processed image</span>
+        <img id="destImg" width="380" height="270" crossorigin />
     </div>
 </div>
 <br />
@@ -54,30 +54,6 @@ Rewrite to utilize simple image-based processing steps.
 <hr  />
 <div id="sourceRibbon" class="mediaRibbon">
     <span class="tiny">Select an image for analysis or upload your own!</span><br />
-    <div>
-        <img src="images/face_reunion.jpg" />
-        <span><a href="https://flic.kr/p/bEgYbs" target="_new">flickr source</a></span>
-    </div>
-    <div>
-        <img src="images/face_family.jpg" />
-        <span><a href="https://www.pexels.com/photo/adult-affection-beautiful-beauty-265764/" target="_new">pexels source</a></span>
-    </div>
-    <div>
-        <img src="images/commercial.jpg" movie="images/commercial.mp4" />
-        <span><a href="https://www.youtube.com/watch?v=34KfCNapnUg" target="_new">youtube source</a></span>
-    </div>
-    <div>
-        <img src="images/face_Schwarzenegger.jpg" />
-        <span><a href="https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/A._Schwarzenegger.jpg/220px-A._Schwarzenegger.jpg" target="_new">wikimedia source</a></span>
-    </div>
-    <div>
-        <img src="images/face_DeGeneres.jpg" />
-        <span><a href="https://en.wikipedia.org/wiki/Ellen_DeGeneres#/media/File:Ellen_DeGeneres-2009.jpg" target="_new">wikipedia source</a></span>
-    </div>
-    <div>
-        <label>Custom Upload</label><br/>
-        <input type="file" id="imageLoader" name="imageLoader"/>
-    </div>
 </div>
     <br />
     <span class="tiny"><em>Note: These sample images are copyright of their original authors and are provided
@@ -110,7 +86,7 @@ Rewrite to utilize simple image-based processing steps.
     Face privacy processing using <em>detected faces</em> and a subsequent processing operation.
 <br/>
 <br/>
-<a href='http://www.research.att.com/projects/Video/'>Video and Multimedia Technologies Research</a>
+<a href='http://www.research.att.com/sites/labs_research/video_media_analytics'>Video and Multimedia Technologies Research</a>
 </div>
 <!-- move script to buttom for faster page load --->
 <script type="text/javascript" src="jquery.js"></script>
index 548dc10..3eeeb42 100644 (file)
@@ -44,7 +44,34 @@ $(document).ready(function() {
         urlDefault = "http://localhost:8884/classify";
     demo_init({
         classificationServer: urlDefault,
-        protoList: [["model.pixelate.proto", true], ["model.detect.proto", false], ["model.recognize.proto", false] ]
+        protoList: [["model.pixelate.proto", true], ["model.detect.proto", false], ["model.recognize.proto", false] ],
+        mediaList: [
+            {
+                'img': 'images/face_reunion.jpg',
+                'source': 'https://flic.kr/p/bEgYbs',
+                'name': 'reuninon (flickr)'
+            },
+            {
+                'img': 'images/face_family.jpg',
+                'source': 'https://www.pexels.com/photo/adult-affection-beautiful-beauty-265764',
+                'name': 'family (pexels)'
+            },
+            {
+                'img': 'images/commercial.jpg',
+                'movie': "images/commercial.mp4",
+                'source': 'https://www.youtube.com/watch?v=34KfCNapnUg',
+                'name': 'family (pexels)'
+            },
+            {
+                'img': 'images/face_Schwarzenegger.jpg',
+                'source': 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/A._Schwarzenegger.jpg/220px-A._Schwarzenegger.jpg',
+                'name': 'Schwarzenegger (wikipedia)'
+            },            {
+                'img': 'images/face_DeGeneres.jpg',
+                'source': 'https://en.wikipedia.org/wiki/Ellen_DeGeneres#/media/File:Ellen_DeGeneres-2009.jpg',
+                'name': 'DeGeneres (wikipedia)'
+            },
+        ]
     });
 });
 
@@ -137,6 +164,11 @@ function processResult(data, dstDiv, methodKeys, dstImg, imgPlaceholder) {
                 domRow.append($("<td />").html(!field_data[1] ?
                     val[field_data[0]] : val[field_data[0]].length + " items"));
             });
+            if (val.x && val.y) {  //valid bounding box examples?
+                canvas_rect(false, val.x, val.y, val.w, val.h, hd.colorSet[idx % hd.colorSet.length]);
+                domRow.children(":nth-child(2)").append($("<div class='colorblock'/>").css(
+                    "background-color", hd.colorSet[idx % hd.colorSet.length]));
+            }
             domTable.append(domRow);
         });
         dstDiv.empty().append($("<strong />").html("Results")).show();
@@ -165,3 +197,5 @@ function processResult(data, dstDiv, methodKeys, dstImg, imgPlaceholder) {
         dstImg.attr('src', "data:"+respImage['mime_type']+";base64,"+respImage['image_binary']).removeClass('workingImage');
     }
 }
+
+
diff --git a/web_demo/spinnerbar.gif b/web_demo/spinnerbar.gif
new file mode 100644 (file)
index 0000000..194ff87
Binary files /dev/null and b/web_demo/spinnerbar.gif differ