2016年4月10日 星期日

找到了個不錯的command line輔助工具~inquirer

inquirer是一套讓您可以製作互動指令的工具,透過inquirer可以達到像是node interpreter的功能。一般在做互動式安裝系統時,需要多次設定參數與環境,或選擇不同的安裝項目時,非常有用ㄛ。

Github repository

Installation

npm install inquirer

Sample Usae

怎麼使用inquirer呢?最簡單就是來個echo的程式,也就是執行後,你打什麼,他就回覆什麼 :D
File: echo.js
#!/usr/bin/env node
var inquirer = require('inquirer');

function it() {
  inquirer.prompt([{
    type: 'input',
    name: 'cmd',
    message: ' →',
  }]).then(function (a) {
    if(a.cmd == 'exit') process.exit(0);
    console.log(a.cmd);
    it();
  });
}

it();
執行狀況:
$ node examples/inquirer/echo.js
?  → hello
hello
?  → Hello Simon!
Hello Simon!
?  → exit
上面的程式中,prompt的輸入參數為需要帶入互動模式的指令,可以指定input, confirm, list...等方法,也可以帶入一些判斷讓指令模式更加聰明。
在then的callback中,所帶入的數值會以input裡面的name為名稱附加在input參數"a"中,例如上方input的name為cmd,則下方then callback的"a"所接收到的參數會帶入在a中...
a = { "cmd":"your-input-text" }
透過callback再呼叫原函式,則可以持續會到interactive模式,只有接收到'exit'時候會直接離開程式。

Reference

2016年2月1日 星期一

用Node.js搭配Google Vision API與Canvas套件,做到人臉辨識功能!

Canvas是HTML5上處理2D影像的常用套件,而在node.js下,可以透過node-canvas來針對影像做一些特殊處理,例如:針對給定的座標畫各式幾何圖形、圖片縮圖等等。
Google Vision API目前是Trust Preview的階段,如果有想要試用,可以在Google的官網(http://cloud.google.com/vision/)申請唷!本篇還是以node-canvas的繪圖為主~ :D

Github

Installation

npm install node-canvas
node-canvas是需要搭配canvas相關函式庫來啟動,相關的安裝方式可以透過官方的github位置來安裝相關套件,以Mac來說,安裝如下:
$ brew install pkg-config cairo libpng jpeg giflib

範例程式

這邊搭配google-vision-api-client套件來查詢Vision API做人臉偵測的動作,並在人的臉部做方型框框標示。
#!/usr/bin/env node
var vision = require('google-vision-api-client');
var requtil = vision.requtil;
var request = require('request');
var Canvas = require('canvas')
  , Image = Canvas.Image
  , fs = require('fs')
  , sizeOf = require('image-size');

var tmpfile = '/tmp/mytmpfile';

var canvas = new Canvas(200, 200)
  , ctx = canvas.getContext('2d')

var jsonfile = '/Users/peihsinsu/.gcpkeys/itri-smart-home/itri-smart-home-33f5a755a360.json';
vision.init(jsonfile);

var outfile = process.argv[3] || '/tmp/test.jpg';

function getColor(i) {
  var list = ['red', 'yellow', 'pink', 'green', 'blue'];
  return list[i%list.length];
}

exports.draw = draw;
function draw(imgfile, outfile, opts, cb){
  fs.readFile(imgfile, function(err, squid){
    if (err) throw err;
    img = new Image;
    img.src = squid;

    if (opts && opts.filter) {
      opts.filter(ctx);
    }
    ctx.drawImage(img , 0, 0, img.width , img.height );

    ctx.globalAlpha = .5;

    var i = 0;
    function doit(vec) {
      var color = Math.random()*1000%255;
      ctx.lineWidth = 4;
      ctx.beginPath();
      ctx.moveTo(vec[0].x, vec[0].y);
      ctx.lineTo(vec[1].x, vec[1].y);
      ctx.lineTo(vec[2].x, vec[2].y);
      ctx.lineTo(vec[3].x, vec[3].y);
      ctx.closePath();
      ctx.strokeStyle = getColor(i);//'rgb(255,color,0)';
      i++;
      ctx.stroke();
      //ctx.fill();
    }

    opts.vec.forEach(function(v) {
      doit(v)
    });
    fs.writeFileSync(outfile, canvas.toBuffer());
    cb();
  });
}

function main(imgfile) {
  var d = requtil.createRequests().addRequest(
    requtil.createRequest(imgfile)
      .withFeature('FACE_DETECTION', 50)
      //.withFeature('LABEL_DETECTION', 20)
      .build());

  var imgSize = sizeOf(imgfile);
  console.log('Got the image size: %sx%s', imgSize.width, imgSize.height);

  vision.query(d, function(e, r, d){
    if(e) return console.log('ERROR:', e);
    console.log(JSON.stringify(d));

    if(!d.responses[0].faceAnnotations) return;

    //var v = d.responses[0].faceAnnotations[0].boundingPoly.vertices;
    var v = [];
    d.responses[0].faceAnnotations.forEach(function(o){
      v.push(o.boundingPoly.vertices);
    })
    console.log('-->', v);
    canvas.width = imgSize.width;
    canvas.height = imgSize.height;

    draw(imgfile, outfile, {
        vec: v,
        filter: function(ctx) {
          //ctx.strokeStyle = 'rgba(255,0,0,.5)';
        }
      },
      function(err) {
        if(err) console.log('ERROR:', err);
    });
  });
}

//Execute the process
if(process.argv[2].indexOf('http') == 0) { //from http resource
  var url = process.argv[2];
  var req = request.get(url);
  req.pipe(fs.createWriteStream(tmpfile));
  req.on('end', function(){
    main(tmpfile);
  });
} else { //from localhost
  main(process.argv[2]);
}
上面程式碼儲存成test.js並給予執行權限後,即可測試...
測試http網站圖片:
$ node test http://www.kevinparker.com.au/people/p7IGM_images/fullsize/shutterstock_48737587_fs.jpg
測試本地圖片:
$ node test /Users/peihsinsu/Pictures/Simon/simon01.jpg
執行完後,會在/tmp/test.jpg產生圖片檔案
$ open /tmp/test.jpg

Notes

Node.js搭配/dev/watchdog做監控

Linux上的Watchdog是一個滿常用來監控系統的服務,可以透過掛載模組後產生硬體的watchdog裝置(/dev/watchdog),掛載方式可以參考本文最下面的鏈結。

其中需要:
1. 設定watchdog的module掛載
2. 啟用module

而在Node.js中需要連結/dev/watchdog的方法,是open該file後,持續的在所規定的時間內丟一個特定字串給它(/dev/watchdog),下面是node.js實作的方式:

var fs = require('fs');
var data = 0;
var WDIOC_KEEPALIVE = 2147768069;
var WDIOC_SETTIMEOUT = 3221509894;
var WDIOC_GETTIMEOUT = 2147768071;

var buf = new Buffer(4);

fs.open('/dev/watchdog', 'r+', function(err, fd){
  if(err) console.log('error:', err);
  setInterval(function() {
    console.log('write file...');
          fs.write(fd, WDIOC_KEEPALIVE, buf, function(err){
            if(err) console.log('error:', err);
          })
  }, 2000);
});

當程式執行起來後,將會每2秒鐘到該裝置寫一塊資料,當成是關閉後,則會觸動系統重新開機的動作... 這個用途在當系統發生問題時候,需要重新開機時,非常有用唷 :D

參考:http://www.switchdoc.com/2014/11/reliable-projects-using-internal-watchdog-timer-raspberry-pi/

2015年12月11日 星期五

用Node.js玩OpenCV

OpenCV是一套有名的影像處理工具,透過node.js來搭配OpenCV可以延伸在node.js在影像相關範圍的應用,例如人臉辨識之類的...

Installation

npm install node-opencv
在安裝node-opencv之前,需要先完成opencv套件的安裝,可以參考node-opencv來做相關安裝。我在Mac上是這樣裝的:
brew tap homebrew/science
brew install opencv

範例程式

下面是一個簡單的範例程式,可以抓http資源位置或是本地端位置的圖片來做人臉的註記(在人臉不用橢圓形標記起來)
#!/usr/bin/env node
var cv = require('opencv');
var request = require('request');
var fs = require('fs');
var tmpfile = '/tmp/tmpgile';

function detect(imgfile) {
  cv.readImage(imgfile, function(err, im){
    im.detectObject(cv.FACE_CASCADE, {}, function(err, faces){
      for (var i=0;i<faces.length; i++){
        var x = faces[i]
        console.log('>>', x); //Will see something like: { x: 336, y: 1359, width: 42, height: 42 }
        im.ellipse(x.x + x.width/2, x.y + x.height/2, x.width/2, x.height/2);
      }
      im.save('/tmp/test.jpg');
    });
  })
}

//Execute the process
if(process.argv[2].indexOf('http') == 0) { //from http resource
  var url = process.argv[2];
  var req = request.get(url);
  req.pipe(fs.createWriteStream(tmpfile));
  req.on('end', function(){
    detect(tmpfile);
  });
} else { //from localhost
  detect(process.argv[2]);
}
上面程式存成:face-opencv.js,並給予執行的權限:
$ ./face-opencv.js http://www.pngdot.com/wp-content/uploads/2015/10/People_Png_Transparent_01.png
回覆的結果:
>> { x: 336, y: 1359, width: 42, height: 42 }
>> { x: 322, y: 137, width: 115, height: 115 }
>> { x: 1255, y: 188, width: 109, height: 109 }
>> { x: 1483, y: 93, width: 125, height: 125 }
>> { x: 584, y: 152, width: 111, height: 111 }
>> { x: 933, y: 156, width: 120, height: 120 }
回覆的部分,還會包含/tmp/test.jpg的輸出檔案,而console輸出的訊息代表每個人臉的中心位置以及範圍,透過這些資訊,可以做更多的應用唷!

2015年8月29日 星期六

Sails ORM簡介

Sails ORM

Sails除了API的概念外,他也可以快速地提供Object Relational Model(ORM)的對應,只要透過下面幾個步驟:

說明

Sails的ORM對應是透過幾個部分做設定就可以完成:
  • config/connection.js:設定所要連線的資料庫位置,裡面會有許多的Adaptor的設定,可供後續選用。
  • config/models.js:設定預設的資料庫模組使用哪個Adaptor。
  • api/models/[Object Name].js:透過"sails generate api [Object Name]"所產生的ORM物件,裡面可以再去客製化所擁有的欄位屬性,以結合未來資料輸入的驗證階段使用。

以MySQL資料庫為例

準備

在使用之前,我們先透過下面指令啟動Docker的MySQL服務...(關於Docker部分,可以參考:https://www.gitbook.com/book/peihsinsu/docker-note-book/details)
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql
啟動無誤後,即可在本機使用MySQL服務。(由於一般Mac或Windows是透過boot2docker來啟用docker,所以在連線時候,需要知道Docker Host的位置作為連線使用)。
$ echo $DOCKER_HOST
tcp://192.168.59.103:2376
上面是Docker Host的回覆訊息,其中192.168.59.103即是Docker的連線位置。

Step1: 安裝相關的資料庫模組

在config/connection.js中設定所要連接的MySQL connection位置,設定大致如下:
module.exports.connections = {
  ... (skip)

  /***************************************************************************
  *                                                                          *
  * MySQL is the world's most popular relational database.                   *
  * http://en.wikipedia.org/wiki/MySQL                                       *
  *                                                                          *
  * Run: npm install sails-mysql                                             *
  *                                                                          *
  ***************************************************************************/
  myMysqlServer: {
    adapter: 'sails-mysql',
    host: '192.168.59.103',
    user: 'root',
    password: 'password',
    database: 'mydb'
  },

  ...(skip)

Step2: 設定預設使用的模組

module.exports.models = {
  connection: 'myMysqlServer'
};

Step3: 安裝sails mysql plugin

$ cd $PROJECT_FOLDER
$ npm install sails-mysql --save
sails-mysql@0.11.0 node_modules/sails-mysql
├── waterline-errors@0.10.1
├── async@1.3.0
├── waterline-sequel@0.5.0
├── waterline-cursor@0.0.5 (async@0.9.2, lodash@2.4.2)
├── lodash@3.10.1
└── mysql@2.8.0 (bignumber.js@2.0.7, readable-stream@1.1.13)

Step4: 建立ORM

建立User module

$ sails generate api user
修改user module的欄位
module.exports = {
  adapter: 'myMysqlServer',
  migrate: 'safe',
  id: {
    primaryKey: true,
    type: 'string'
  },
  attributes: {
    name: {
      type: 'string'
    },

    age: {
      type: 'float',
      required: true
    },

    password: {
      type: 'string',
      required: true
    }

  }
}

建立資料庫與對應欄位

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `password` varchar(200) DEFAULT NULL,
  `createdAt` datetime DEFAULT NULL,
  `updatedAt` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;

Step5: 啟動並測試

透過Sails的RESTful API定義,要Insert一筆User資料到Sails ORM的對應資料庫中,可以透過下面POST method的方法:
curl -sS http://localhost:1337/user/create -d name=simon -d age=36 -d password=123 -d id=123 -X POST | json
{
  "name": "simon",
  "age": 36,
  "password": "123",
  "id": 123,
  "createdAt": "2015-08-30T05:04:02.291Z",
  "updatedAt": "2015-08-30T05:04:02.291Z"
}
當Inser完,需要檢視資料的狀況,可以透過GET method取回該資料集的內容:
$ curl -sS http://localhost:1337/user | json
[
  {
    "name": "simon",
    "age": 36,
    "password": "123",
    "id": 1,
    "createdAt": "2015-08-30T02:46:54.000Z",
    "updatedAt": "2015-08-30T02:46:54.000Z"
  },
  {
    "name": "simon",
    "age": 36,
    "password": "123",
    "id": 2,
    "createdAt": "2015-08-30T02:47:28.000Z",
    "updatedAt": "2015-08-30T02:47:28.000Z"
  }
]
如果您知道該筆資料的ID,則可以透過下面方式給定:
curl -sS http://localhost:1337/user/123 | json
{
  "name": "simon",
  "age": 36,
  "password": "123",
  "id": 123,
  "createdAt": "2015-08-30T05:04:02.000Z",
  "updatedAt": "2015-08-30T05:04:02.000Z"
}

以CouchDB資料庫為例

Sails抽離了大部份的設定,而ORM connection的設定抽離讓未來切換到其他的資料源可以更方便。下面是從MySQL切換到CouchDB的方式:

Step1: 安裝相關的資料庫模組

在config/connection.js中設定所要連接的CouchDB connection位置,設定大致如下:
module.exports.connections = {
  ... (skip)

  couch: {
    adapter: 'sails-couchdb-orm',
    host: '192.168.59.103',
    port: 5984,
    username: 'admin',
    password: 'adminadmin'
  },

  ...(skip)

Step2: 設定預設使用的模組

module.exports.models = {
  connection: 'couch'
};

Step3: 安裝sails couchdb plugin

$ cd $PROJECT_FOLDER
$ npm install sails-couchdb-orm --save

Step4: 建立ORM

在CouchDB中,由於是屬於NoSQL的性質,我們不需要是先建立資料庫以及對應的Schema。而ORM的設定檔案部分,直接依據上面所設定的部分即可:
module.exports = {
  adapter: 'couch',
  migrate: 'safe',
  id: {
    primaryKey: true,
    type: 'string'
  },
  attributes: {
    name: {
      type: 'string'
    },

    age: {
      type: 'float',
      required: true
    },

    password: {
      type: 'string',
      required: true
    }

  }
}

Step5: 啟動並測試

整體設定完成後,直接依照MySQL部分的啟動測試程序執行,應該就可以在CouchDB中看到對應的資料庫物件與輸入的內容。

其他參考資訊

2015年8月16日 星期日

Node.js的強大Web Framework - SailsJS

sails

sails是一套功能強大的web framework,他有express的template generate功能,也具備restful api的產生功能,更可以跟後端的資料庫進行restful api與orm對應整合...

Github repository

Installation

sudo npm -g install sails

Sample Usage

Sails的指令語法與express相仿,下面介紹他的基本使用:

建立新專案

sails new [project name]

啟動專案

一般express的專案會需要在建立完之後做"npm install"的套件安裝動作,但在sails中,他會直接用symbolic link將套件鏈結到sails已經有的套件位置,讓整個安裝與啟動過程可以更加快速。
cd $project
sails lift

使用auto generate的restful api

建立一個user物件

在該application建立user物件api:
sails generate api user
執行呼叫建立user物件:
curl http://localhost:1337/user/create
如果一切無誤,會看到下面的回覆:
{"createdAt":"2015-08-15T17:28:53.366Z","updatedAt":"2015-08-15T17:28:53.366Z","id":1}
查詢所有的user列表:
curl http://localhost:1337/user/find
回覆如下:
[{"createdAt":"2015-08-15T17:28:53.366Z","updatedAt":"2015-08-15T17:28:53.366Z","id":1}]
查詢id=1的user
curl http://localhost:1337/user/1
如果該id可以查得到對應的物件,則會就該物件資訊回覆
{"createdAt":"2015-08-15T17:28:53.366Z","updatedAt":"2015-08-15T17:28:53.366Z","id":1}
而針對查詢不到的id,則會有以下訊息:
$ curl http://localhost:1337/user/3
No record found with the specified `id`.

開發一個頁面

對照express的使用方式,express是直接在app.js中建立routing(ex: app.get('/api/:id', ...))或是express4中,在routing資料夾中建立對應的routing然後再link回app.js,並且對應頁面樣板到views資料夾下面。 而sails中,則是在config/routes.js中定義routing,而所指定的頁面樣板,一樣指定到views資料夾下。

在Express中

Express - app.js
app.get('/test/:data', function(req, res, next){
    res.render('result', {data:'api data:' + req.params.data});
})
Express - views/result.ejs
<%- data %>

在Sails中

Sails - config/routes.js
module.exports.routes = {
  '/test/:data':
    { controller: 'Result', action: 'hello' }
}
這邊需要注意,在我們指定controller的部分,Result是一個short name,實際建立檔案時候需要用ResultController.js,Sails才找得到這個檔案...,除了這樣寫,也可以用下面的方式:
直接使用字串方式,用short name呈現:
'/test/:data': 'Result.hello'
直接使用字串方式,用full name呈現:
'/test/:data': 'ResultController.hello'
或是透過json方式並以玩逞名稱定義:
'/test/:data':
    { controller: 'ResultController', action: 'hello' }
另外,可以在routing前面指定request method:
'POST /test/:data': 'Result.hello'
或是用萬用字元處理:
'POST /test/*': 'Result.hello'
Sails - api/controllers/ResultController.js
module.exports = {
  hello: function(req, res) {
    return res.send(JSON.stringify({data: '123'}));
  }
}
除果希望抓到routing所傳入的參數,可以透過req.params['param_name']的方式來抓:
module.exports = {
  hello: function(req, res) {
    return res.send(JSON.stringify({data: req.params.data}));
  }
}

資料與頁面樣板整合

一般我們會在Controller中去存取資料庫資料,然後再帶到頁面去做rendering,在Sails可以這樣做:
Sails - api/controllers/ResultController.js
module.exports = {
  hello: function(req, res) {
    var data = req.params.data;
    res.view('result', {data: data});
  }
}
其中res.view()的第一個參數是頁面樣板的名稱,通常我們放在views資料夾中;第二個參數就是我們帶入的資料...
Sails - views/result.js
頁面的資料存取,則以ejs的語法即可,例如上面傳入的json中有個data,這邊就可以直接用data來接值...
The data is: <%- data %>

參考