Переглянути джерело

Completed Photo module, added WIP Nightly API call to agi interface

Toby Chui 3 роки тому
батько
коміт
87008a5ec7

+ 3 - 0
AGI Documentation.md

@@ -215,6 +215,9 @@ one of these functions has to be called.
 ```
 sendResp(string)	=> Response header with text/plain header
 sendJSONResp(json_string) => Response request with JSON header
+
+//Since v1.119
+sendJSONResp(object) => Overload function, allow the same API to send Javascript object directly without the need for manual stringify using JSON.stringify
 ```
 
 Customize header:

+ 1 - 0
agi.go

@@ -25,6 +25,7 @@ func AGIInit() {
 		ActivateScope:        []string{"./web", "./subservice"},
 		FileSystemRender:     thumbRenderHandler,
 		ShareManager:         shareManager,
+		NightlyManager:       nightlyManager,
 	})
 	if err != nil {
 		log.Println("AGI Gateway Initialization Failed")

+ 31 - 0
mod/agi/agi.go

@@ -15,6 +15,7 @@ import (
 	metadata "imuslab.com/arozos/mod/filesystem/metadata"
 	"imuslab.com/arozos/mod/iot"
 	"imuslab.com/arozos/mod/share"
+	"imuslab.com/arozos/mod/time/nightly"
 	user "imuslab.com/arozos/mod/user"
 )
 
@@ -49,6 +50,7 @@ type AgiSysInfo struct {
 	FileSystemRender     *metadata.RenderHandler
 	IotManager           *iot.Manager
 	ShareManager         *share.Manager
+	NightlyManager       *nightly.TaskManager
 
 	//Scanning Roots
 	StartupRoot   string
@@ -57,6 +59,7 @@ type AgiSysInfo struct {
 
 type Gateway struct {
 	ReservedTables   []string
+	NightlyScripts   []string
 	AllowAccessPkgs  map[string][]AgiPackage
 	LoadedAGILibrary map[string]AgiLibIntergface
 	Option           *AgiSysInfo
@@ -66,6 +69,7 @@ func NewGateway(option AgiSysInfo) (*Gateway, error) {
 	//Handle startup registration of ajgi modules
 	gatewayObject := Gateway{
 		ReservedTables:   option.ReservedTables,
+		NightlyScripts:   []string{},
 		AllowAccessPkgs:  map[string][]AgiPackage{},
 		LoadedAGILibrary: map[string]AgiLibIntergface{},
 		Option:           &option,
@@ -73,6 +77,7 @@ func NewGateway(option AgiSysInfo) (*Gateway, error) {
 
 	//Start all WebApps Registration
 	gatewayObject.InitiateAllWebAppModules()
+	gatewayObject.RegisterNightlyOperations()
 
 	//Load all the other libs entry points into the memoary
 	gatewayObject.ImageLibRegister()
@@ -85,6 +90,32 @@ func NewGateway(option AgiSysInfo) (*Gateway, error) {
 	return &gatewayObject, nil
 }
 
+func (g *Gateway) RegisterNightlyOperations() {
+	g.Option.NightlyManager.RegisterNightlyTask(func() {
+		//This function will execute nightly
+		for _, scriptFile := range g.NightlyScripts {
+			if isValidAGIScript(scriptFile) {
+				//Valid script file. Execute it with system
+				for _, username := range g.Option.UserHandler.GetAuthAgent().ListUsers() {
+					userinfo, err := g.Option.UserHandler.GetUserInfoFromUsername(username)
+					if err != nil {
+						continue
+					}
+
+					if checkUserAccessToScript(userinfo, scriptFile, "") {
+						//This user can access the module that provide this script.
+						//Execute this script on his account.
+						log.Println("[AGI_Nightly] WIP (" + scriptFile + ")")
+					}
+				}
+			} else {
+				//Invalid script. Skipping
+				log.Println("[AGI_Nightly] Invalid script file: " + scriptFile)
+			}
+		}
+	})
+}
+
 func (g *Gateway) InitiateAllWebAppModules() {
 	startupScripts, _ := filepath.Glob(filepath.ToSlash(filepath.Clean(g.Option.StartupRoot)) + "/*/init.agi")
 	for _, script := range startupScripts {

+ 5 - 0
mod/agi/static.go

@@ -17,6 +17,11 @@ func checkUserAccessToScript(thisuser *user.User, scriptFile string, scriptScope
 	return true
 }
 
+//validate the given path is a script from webroot
+func isValidAGIScript(scriptPath string) bool {
+	return fileExists(filepath.Join("./web", scriptPath)) && (filepath.Ext(scriptPath) == ".js" || filepath.Ext(scriptPath) == ".agi")
+}
+
 //Return the script root of the current executing script
 func getScriptRoot(scriptFile string, scriptScope string) string {
 	//Get the script root from the script path

+ 21 - 1
mod/agi/systemFunc.go

@@ -39,13 +39,33 @@ func (g *Gateway) injectStandardLibs(vm *otto.Otto, scriptFile string, scriptSco
 		return otto.Value{}
 	})
 
-	vm.Set("sendJSONResp", func(call otto.FunctionCall) otto.Value {
+	vm.Set("_sendJSONResp", func(call otto.FunctionCall) otto.Value {
 		argString, _ := call.Argument(0).ToString()
 		vm.Set("HTTP_HEADER", "application/json")
 		vm.Set("HTTP_RESP", argString)
 		return otto.Value{}
 	})
 
+	vm.Run(`
+		sendJSONResp = function(object){
+			if (typeof(object) === "object"){
+				_sendJSONResp(JSON.stringify(object));
+			}else{
+				_sendJSONResp(object);
+			}
+		}
+	`)
+
+	vm.Set("addNightlyTask", func(call otto.FunctionCall) otto.Value {
+		scriptPath, _ := call.Argument(0).ToString() //From web directory
+		if isValidAGIScript(scriptPath) {
+			g.NightlyScripts = append(g.NightlyScripts, scriptPath)
+		} else {
+			return otto.FalseValue()
+		}
+		return otto.TrueValue()
+	})
+
 	//Database related
 	//newDBTableIfNotExists(tableName)
 	vm.Set("newDBTableIfNotExists", func(call otto.FunctionCall) otto.Value {

BIN
system/neuralnet/predictions.jpg


+ 52 - 4
web/Photo/backend/classify.js

@@ -4,34 +4,82 @@
     Image Classification Script
     Generate classification for the given image path
 
+    Paramters:
+    ws (Optional), upgrade this connection to websocket for realtime progress viewing
 */
 
 
 requirelib("filelib");
 requirelib("imagelib");
+requirelib("websocket");
 includes("imagedb.js");
 
+
+
 function returnError(msg){
     sendJSONResp(JSON.stringify({"error": msg}));
 }
 
+function init(){
+    newDBTableIfNotExists("photo");
+}
+
 function main(){
     var roots = getAllPossibleRoots();
     for (var i = 0; i < roots.length; i++){
-        var thisVroot = roots[i];
+        var thisVroot = roots[i][1];
+
         //Load all tags record for this vroot
         var tagsRecords = loadAllTagsRecord(thisVroot);
 
         //Clear up all images tags that no longer exists
         tagsRecords = matchAndClearNonExistsRecords(tagsRecords);
-        //Do resummarize of tags info
+        
+        //Convert it to a path:value keypair 
+        var filepathMap = summarizeAndRestructureFilepaths(tagsRecords);
+        //sendResp(JSON.stringify(filepathMap));
 
         //Scan for new images that is not classified and add them to the root tag file
+        var allValidImagesInThisDir = getAllImageFiles();
+        var websocketMode = false;
+        if (typeof(ws) != "undefined"){
+            websocketMode = true;
+            websocket.upgrade(10);
+            delay(100);
+        }
+        var counter = 1;
+        for ( var i = 0; i < allValidImagesInThisDir.length; i++){
+            var thisImageFile = allValidImagesInThisDir[i];
+
+            //Check if this filepath is already been classified.
+            if (typeof(filepathMap[thisImageFile]) == "undefined"){
+                //Not found in cache. New photo! 
+                console.log("[Photo] Analysising photo with neuralnet: " + thisImageFile);
+                var thisImageTagRecord = getImageTagsRecord(thisImageFile);
+                if (websocketMode){
+                    websocket.send(JSON.stringify(thisImageFile));
+                }
+
+                //Push it into the record
+                tagsRecords.push(thisImageTagRecord);
+                counter++;
+            }
 
-        //
+            if (counter%5 == 0){
+                //Auto save every 5 photos
+                console.log("[Photo] Auto saved")
+                saveAllTagsRecords(thisVroot, tagsRecords);
+            }
+          
+        }
+        //Final save
+        saveAllTagsRecords(thisVroot, tagsRecords);
     }
-   
+    console.log("[Photo] Automatic tag generation - Done")
+    websocket.close();
+    sendResp("OK");
 }
 
+init();
 main();
 

+ 53 - 5
web/Photo/backend/imagedb.js

@@ -53,6 +53,13 @@ function isSupportedImage(filename){
     }
 }
 
+function inCacheFolder(filename){
+    if (filename.indexOf(".cache") >= 0){
+        return true;
+    }
+    return false;
+}
+
 //Get all the image files exists in *:/Photo/*
 function getAllImageFiles(){
     var results = [];
@@ -62,7 +69,7 @@ function getAllImageFiles(){
         var allFilesInThisPhotoRoot = filelib.walk(thisRootInfo[2]);
         for ( var j = 0; j < allFilesInThisPhotoRoot.length; j++){
             var thisFile = allFilesInThisPhotoRoot[j];
-            if (!filelib.isDir(thisFile) && isSupportedImage(thisFile)){
+            if (!filelib.isDir(thisFile) && isSupportedImage(thisFile) && !inCacheFolder(thisFile)){
                 results.push(thisFile);
             }
         }
@@ -73,10 +80,11 @@ function getAllImageFiles(){
 
 //Get the tag of a certain image file given its filepath
 function getImageTags(imagefile){
-    var results = imagelib.classify(imagefile, "darknet19"); 
+    var results = imagelib.classify(imagefile, "yolo3"); 
     var tags = [];
     for (var i = 0; i < results.length; i++){
-        if (results[i].Percentage > 50){
+        console.log(results[i].Name, results[i].Percentage);
+        if (results[i].Percentage > 10){
             //Confidence larger than 50
             tags.push({
                 "object": results[i].Name,
@@ -97,7 +105,7 @@ function getImageTagsRecord(imagefile){
 }
 
 function loadAllTagsRecord(rootID){
-    var tagFile = rootID + ":/Photo/tags.json"
+    var tagFile = rootID + "Photo/tags.json"
     if (filelib.fileExists(tagFile)){
         var tagsData = filelib.readFile(tagFile)
         return JSON.parse(tagsData);
@@ -106,7 +114,7 @@ function loadAllTagsRecord(rootID){
 }
 
 function saveAllTagsRecords(rootID, tagRecords){
-    var tagFile = rootID + ":/Photo/tags.json"
+    var tagFile = rootID + "Photo/tags.json"
     return filelib.writeFile(tagFile, JSON.stringify(tagRecords))
 }
 
@@ -126,3 +134,43 @@ function matchAndClearNonExistsRecords(tagRecords){
     return cleanedTagRecords;
 }
 
+//Translate the record array into keyvalue paris of [filepath](record object)
+function summarizeAndRestructureFilepaths(tagRecords){
+    var filepathMap = {};
+    for ( var i = 0; i < tagRecords.length; i++){
+        var thisRecord = tagRecords[i];
+        filepathMap[thisRecord.filepath] = JSON.parse(JSON.stringify(thisRecord));
+    }
+    return filepathMap;
+}
+
+//Translate the tag array into key-value pairs of [tag](filepath)
+function summarizeAndrestructureTags(tagRecords){
+    var tags = {};
+    for ( var i = 0; i < tagRecords.length; i++){
+        var thisRecord = tagRecords[i];
+        for ( var j = 0; j < thisRecord.tags.length; j++){
+            var thisTag = thisRecord.tags[j];
+            if (typeof(tags[thisTag.object]) == "undefined"){
+                //Not exists. Create it
+                tags[thisTag.object] = [thisRecord.filepath];
+            }else{
+                //Already exists. Remove duplicate
+                var alreadyExists = false;
+                for ( var k = 0; k < tags[thisTag.object].length; k++){
+                    if (tags[thisTag.object][k] == thisRecord.filepath){
+                        alreadyExists = true;
+                    }
+                }
+                if (!alreadyExists){
+                    tags[thisTag.object].push(thisRecord.filepath);
+                }
+                
+                
+            }
+        }
+        
+    }
+
+    return tags;
+}

+ 26 - 0
web/Photo/backend/listTags.js

@@ -0,0 +1,26 @@
+/*
+    List all the tags generated by neuralnet
+
+    List all tags if tag paramter is not defined, otherwise show taged images
+    Require
+    vroot
+*/
+
+includes("imagedb.js");
+
+function getVrootIDfromPath(rootID){
+    if (rootID.indexOf(":") >= 0){
+        rootID = rootID.split(":")[0];
+    }
+
+    return rootID;
+}
+
+function main(){
+    var targetVroot = getVrootIDfromPath(vroot);
+    var tagRecords = loadAllTagsRecord(targetVroot + ":/");
+    var tagsMap = summarizeAndrestructureTags(tagRecords);
+    sendJSONResp(tagsMap);
+}
+
+main();

+ 11 - 9
web/Photo/index.html

@@ -116,17 +116,18 @@
                     
                 </div>
             </div>
-            <!-- 
+
             <div class="item">
-                Tags by AI
-                <div class="menu">
-                    <a class="active item">#a</a>
-                    <a class="item">#b</a>
-                    <a class="item">#c</a>
-                    <a class="item">See More</a>
+                Tags
+                <div class="menu" >
+                    <div style="max-height: 200px; overflow-y: auto;">
+                        <template x-for="key in Object.keys(tags)">
+                            <a class="item" x-on:click="console.log( tags[key][0],tags[key][1]); images = tags[key];" :filedata="encodeURIComponent(JSON.stringify(tags[key]));" ><i class="ui hashtag icon"></i> <span x-text="key"></span></a>
+                        </template>
+                    </div>
+                    <a class="item" onclick="rescan(this);">Rescan <i class="ui search icon"></i></a>
                 </div>
             </div>
-            -->
 
         </div>
 
@@ -142,7 +143,7 @@
             </div>
             <div id="viewboxContainer">
                 <div id="viewbox" class="ui six cards viewbox">
-                    <template x-for="image in images" :key="image">
+                    <template x-for="image in images">
                         <div class="imagecard" style="cursor: pointer;" x-on:click="showImage($el);" :style="{width: renderSize + 'px', height: renderSize + 'px'}" :filedata="encodeURIComponent(JSON.stringify({'filename':image.split('/').pop(),'filepath':image}))">
                             <a class="image" x-init="updateImageSizes();">
                                 <img :src="'../system/file_system/loadThumbnail?bytes=true&vpath=' + image">
@@ -164,6 +165,7 @@
     </div>
 </body>
 <script>
+
     //Calculate image size on document ready
 
     $(window).on("resize ", function() {

+ 49 - 1
web/Photo/photo.js

@@ -63,6 +63,7 @@ function photoListObject() {
         vroots: [],
         images: [],
         folders: [],
+        tags: [],
         
         // init
         init() {
@@ -121,8 +122,11 @@ function photoListObject() {
                         $("#nosubfolder").hide();
                     }
                     console.log(this.folders);
+
+                    
                 });
             });
+            this.getTags();
         },
 
         getRootInfo() {
@@ -138,13 +142,40 @@ function photoListObject() {
                     this.vroots = data;
                 });
             })
+        },
+
+        getTags(){
+            fetch(ao_root + "system/ajgi/interface?script=Photo/backend/listTags.js", {
+                method: 'POST',
+                cache: 'no-cache',
+                headers: {
+                  'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({
+                    "vroot": this.currentPath
+                })
+            }).then(resp => {
+                resp.json().then(data => {
+                    this.tags = data;
+                });
+            });
         }
     }
 }
 
+function renderImageList(object){
+    var fd = $(object).attr("filedata");
+    fd = JSON.parse(decodeURIComponent(fd));
+    console.log(fd);
+    
+}
 
 function ShowModal(){
-    $('.ui.modal').modal('show');
+    $('.ui.modal').modal({
+        onHide: function(){
+            ao_module_setWindowTitle("Photo");
+        }
+    }).modal('show');
 }
 
 function showImage(object){
@@ -160,6 +191,23 @@ function showImage(object){
     if (prevCard.length > 0){
 
     }
+
+    ao_module_setWindowTitle("Photo - " + fd.filename);
     
     console.log(nextCard, prevCard);
+}
+
+function rescan(object){
+    var originalContent = $(object).html();
+    $(object).html(`<i class="ui spinner loading icon"></i> Analysing`);
+    ao_module_agirun("Photo/backend/classify.js", {
+
+    }, function(data){
+        //Done
+        $(object).html(`<i class="ui green checkmark icon"></i> Done`);
+        setTimeout(function(){
+            $(object).html(originalContent);
+        }, 3000);
+        
+    });
 }

+ 5 - 0
web/UnitTest/index.html

@@ -28,6 +28,7 @@
 	<br><br>
 		<div class="ui text container">
 			<h3>AGI Unit Testing Module</h3>
+            <button class="ui button" onclick="openGateway();">Open AGI Gateway (No File)</button>
             <button class="ui button" onclick="runtest();">Run Unit Test</button>
             <a class="ui button" href="wstest.html">Open WebSocket Test</a>
             <div class="ui divider"></div>
@@ -45,6 +46,10 @@
 		</div>
         <br><br>
        <script>
+           function openGateway(){
+                window.open("../system/ajgi/interface");
+           }
+
 			function runtest(){
 				$("#results").html("");
 				//Get a list of test to be run