From bcccb30213231bdcf84d2dd381fde7c435f8df00 Mon Sep 17 00:00:00 2001
From: Bilasco Ioan Marius <marius.bilasco@univ-lille1.fr>
Date: Mon, 19 Feb 2018 12:20:26 +0100
Subject: [PATCH] TP3 : meanshift, difference, mvt, morpho, blobs

---
 animation/moving_circle.js                    | 118 ++++++++++++++++++
 data/red_circle.gif                           | Bin 0 -> 2318 bytes
 data/red_circle.jpg                           | Bin 0 -> 3779 bytes
 data/red_circle.png                           | Bin 0 -> 3919 bytes
 effects/focus.js                              | 117 +++++++++++++++++
 effects/generic.js                            |  19 +++
 effects/morpho.js                             |  42 +++++++
 features/pixels.js                            |  41 +++++-
 filters/linear.js                             |   6 +
 filters/morpho.js                             |  84 +++++++++++++
 metrics/pixels.js                             |  34 ++++-
 movement/detection.js                         |  55 ++++++++
 movement/difference.js                        |  77 ++++++++++++
 processing.js                                 |  16 +--
 processing2.js                                |  17 ++-
 samples/blobs_appearance.html                 |  38 ++++++
 .../blobs_diff_img_dilatation_erosion.html    |  71 +++++++++++
 samples/blobs_diff_img_no_normalization.html  |  61 +++++++++
 .../blobs_diff_img_with_normalization.html    |  61 +++++++++
 samples/difference_animation.html             |  43 +++++++
 samples/difference_effect.html                |  47 +++++++
 samples/difference_video.html                 |  31 +++++
 ...eanshift_changing_circle_and_bg_color.html |  66 ++++++++++
 samples/meanshift_changing_circle_color.html  |  62 +++++++++
 samples/meanshift_circle.html                 |  56 +++++++++
 samples/morpho_dilatation.html                |   0
 samples/morpho_dilatation_erosion.html        |  49 ++++++++
 samples/mvt_detection_region.html             |  62 +++++++++
 segmentation/blobs.js                         |  93 ++++++++++++++
 tools.js                                      |  47 +++++++
 tracking/appearance.js                        |  87 +++++++++++++
 31 files changed, 1485 insertions(+), 15 deletions(-)
 create mode 100644 animation/moving_circle.js
 create mode 100644 data/red_circle.gif
 create mode 100644 data/red_circle.jpg
 create mode 100644 data/red_circle.png
 create mode 100644 effects/focus.js
 create mode 100644 effects/generic.js
 create mode 100644 effects/morpho.js
 create mode 100644 filters/linear.js
 create mode 100644 filters/morpho.js
 create mode 100644 movement/detection.js
 create mode 100644 movement/difference.js
 create mode 100644 samples/blobs_appearance.html
 create mode 100644 samples/blobs_diff_img_dilatation_erosion.html
 create mode 100644 samples/blobs_diff_img_no_normalization.html
 create mode 100644 samples/blobs_diff_img_with_normalization.html
 create mode 100644 samples/difference_animation.html
 create mode 100644 samples/difference_effect.html
 create mode 100644 samples/difference_video.html
 create mode 100644 samples/meanshift_changing_circle_and_bg_color.html
 create mode 100644 samples/meanshift_changing_circle_color.html
 create mode 100644 samples/meanshift_circle.html
 create mode 100644 samples/morpho_dilatation.html
 create mode 100644 samples/morpho_dilatation_erosion.html
 create mode 100644 samples/mvt_detection_region.html
 create mode 100644 segmentation/blobs.js
 create mode 100644 tracking/appearance.js

diff --git a/animation/moving_circle.js b/animation/moving_circle.js
new file mode 100644
index 0000000..a1402e4
--- /dev/null
+++ b/animation/moving_circle.js
@@ -0,0 +1,118 @@
+var animations=[]
+
+animations.MovingCircle=function(output_canvas_id,opt_options) {
+  this.radius=opt_options&&opt_options.radius?opt_options.radius:5;
+
+  this.x0=opt_options&&opt_options.x0?opt_options.x0:5;
+  this.y0=opt_options&&opt_options.y0?opt_options.y0:5;
+
+  this.step_x=opt_options&&opt_options.step_x?opt_options.step_x:1;
+  this.step_y=opt_options&&opt_options.step_x?opt_options.step_y:0;
+
+  this.random=opt_options&&opt_options.random?true:false;
+
+  this.output_cvs=document.getElementById(output_canvas_id);
+  this.output_ctxt=this.output_cvs.getContext("2d");
+  this.fillColor=opt_options&&opt_options.fillColor?opt_options.fillColor:[255,0,0,255];
+
+}
+
+animations.MovingCircle.prototype.move=function() {
+  var local_dx=Math.round(this.step_x*(!this.random?1:Math.random()));
+  var local_dy=Math.round(this.step_y*(!this.random?1:Math.random()));
+
+  this.x0+=local_dx; this.y0+=local_dy;
+
+  if ((this.x0+this.radius)>this.output_cvs.width) {
+    this.x0=this.output_cvs.width-this.radius; this.step_x=-this.step_x;
+  } else if (this.x0 < this.radius ) {
+      this.x0=this.radius; this.step_x=-this.step_x;
+  }
+
+  if ((this.y0+this.radius) > this.output_cvs.height) {
+    this.y0=this.output_cvs.height-this.radius; this.step_y=-this.step_y;
+  } else if (this.y0 < this.radius ) {
+    this.y0=this.radius; this.step_y=-this.step_y;
+  }
+}
+
+animations.MovingCircle.prototype.draw=function() {
+  this.output_ctxt.fillStyle="rgba("+this.fillColor.join(",")+")";
+  this.output_ctxt.beginPath();
+  this.output_ctxt.arc(this.x0,this.y0,this.radius,0,2*Math.PI);
+  this.output_ctxt.fill();
+}
+animations.MovingCircle.prototype.animate=function() {
+  this.output_ctxt.beginPath();
+  this.output_ctxt.clearRect(this.x0-this.radius,this.y0-this.radius,this.radius*2,this.radius*2);
+  this.move();
+  this.draw();
+}
+
+animations.MovingCircleHChangingColor=function(output_canvas_id,opt_options) {
+  this.__proto__.__proto__=new animations.MovingCircle(output_canvas_id,opt_options);
+
+  this.fillColor0=opt_options&&opt_options.fillColor0?opt_options.fillColor0:[255,0,0,0];
+  this.fillColor1=opt_options&&opt_options.fillColor1?opt_options.fillColor1:[0,255,0,0];
+  this.fillColorNbSteps=opt_options&&opt_options.fillColorNbSteps?opt_options.fillColorNbSteps:5;
+  this.fillColorIncrement=[];
+  for (i=0;i<4;i++) {
+    this.fillColorIncrement[i]=(this.fillColor1[i]-this.fillColor0[i])/this.fillColorNbSteps;
+  };
+  this.fillColor=this.fillColor0;
+  this.fillColorStepCount=0;
+  this.fillColorIncrementSign=1;
+
+}
+
+animations.MovingCircleHChangingColor.prototype.move=function() {
+  this.__proto__.__proto__.move();
+
+  if (this.fillColorStepCount==this.fillColorNbSteps) {
+    this.fillColorIncrementSign=-this.fillColorIncrementSign;
+    this.fillColorStepCount=0;
+  };
+
+  for (i=0;i<4;i++) {
+    this.fillColor[i]+=this.fillColorIncrementSign*this.fillColorIncrement[i];
+  };
+  this.fillColorStepCount++;
+}
+
+animations.MovingCircleHChangingColorChangingBackground=function(output_canvas_id,opt_options) {
+  this.__proto__.__proto__=new animations.MovingCircleHChangingColor(output_canvas_id,opt_options);
+
+  this.bgColor0=opt_options&&opt_options.bgColor0?opt_options.bgColor0:[255,0,0,255];
+  this.bgColor1=opt_options&&opt_options.bgColor1?opt_options.bgColor1:[0,255,0,255];
+  this.bgColorNbSteps=opt_options&&opt_options.bgColorNbSteps?opt_options.bgColorNbSteps:5;
+  this.bgColorIncrement=[];
+  for (i=0;i<4;i++) {
+    this.bgColorIncrement[i]=(this.bgColor1[i]-this.bgColor0[i])/this.bgColorNbSteps;
+  };
+  this.bgColor=this.bgColor0;
+  this.bgColorStepCount=0;
+  this.bgColorIncrementSign=1;
+
+}
+
+animations.MovingCircleHChangingColorChangingBackground.prototype.move=function() {
+  this.__proto__.__proto__.move();
+
+  if (this.bgColorStepCount==this.bgColorNbSteps) {
+    this.bgColorIncrementSign=-this.bgColorIncrementSign;
+    this.bgColorStepCount=0;
+  };
+
+  for (i=0;i<4;i++) {
+    this.bgColor[i]+=this.bgColorIncrementSign*this.bgColorIncrement[i];
+  };
+  this.bgColorStepCount++;
+}
+
+animations.MovingCircleHChangingColorChangingBackground.prototype.animate=function() {
+  this.output_ctxt.fillStyle="rgba("+this.bgColor.join(",")+")";
+  this.output_ctxt.beginPath();
+  this.output_ctxt.fillRect(0,0,this.output_cvs.width,this.output_cvs.height);
+  this.move();
+  this.draw();
+}
diff --git a/data/red_circle.gif b/data/red_circle.gif
new file mode 100644
index 0000000000000000000000000000000000000000..660e416cf70e75545dfafb805ab084eae3a61cfe
GIT binary patch
literal 2318
zcmZ?wbh9u|G-t48n8*ME{}~wm|Nqaxz@Ye(g_D7SnL+VCx2Lmnkh_z+p@E^%e+CBj
z8;J!4IZO%+47qtFMM3UP3K5Y}3hXx-m>47(xEZ7vxD%6$3mgLid_d-b)PiVK{OuY8
zBS_@3E_Qj498S!gl3JY1z`)4Cz`&57Qk<O2z`$6+z`!~uxuB?ofq`9wfq}uIyrclc
z2Dw3`C?Yb7fq^}Nfq_9J9l|aFv6CR|4iLL2B*>Y8fqf1G0|Q@jMq&yB1N#~V1_qrZ
zB=Pi+p#N}15J!L=DdL`*ms*sWtN;p(fTH}g%$!uP1cV?U4N*jp%*`oHhANV0U|`@W
z&JOlwU|`T-U|?)XNpu0R^%xi!*CZ#p1~V`)s4y@v-YUxs^MkVg6_hvyF)%QwF)%PG
zm1c)JGB7Y`F)%QBq!qb^Ld;<*$|&`Kv1e6fgoHCNFo-cQFdfQE^7Du2{m*nGx!5_1
zfq_AZfr05?Mye+$gyk3*m=!WhJVPJ`G20d82Z1!GFfcHurKGyJf<lskfw?O?-y7rz
zF$M<aZN+85AaRg`Zd7GBgB%JHKa-W{6~Ms2AjQDI{3tcY9i(1~fr0r?K}i6}Of?1u
z7P-6}Kajl|3=AwTsl{LqgT+fSLOdWIX2~fj3W4fnnUI$0<_U2d%chJX50E%W@2!Fy
za4OYdU|?k`Dh&d8UWtK$RWGr~)g9tt)_~N!P*8xXF)*-}G5il>Ok_x9$Y)4mNM%T7
z$Yc1+puiBs;LhO0pukYTP{feWkj9Y7ki+24kjjw9kjhZRkjjt)SECEoSIm&jP{feQ
zPzIJOW=LfS0IP)PM3|QjHsb|YZ8C$i+){>81_g!;hBpj(3>k1U6&RcuG8jr3@)*)_
zm;-UkOQ<>dEaEH%EXFJbEY>VOEVe8bES3xkEcPr8EOsm|ECwu=EY@(d1CV^Az>v=H
z@;^f!1H_<0hEj%9u);6~Cx(LzISeHXi3~Xm4;k_p@)?T3G8n-!L8?>(A=6Y8pP78V
z{{vEBWio&Q2NndnC_(BE4m>=8&iaJ)1?yGTC#*MF?;vbpz0G=u^$zPz24v6VBjgz%
z!3>J3Oon8Jd=zysmw-GEiqQWsWsVFv3^`z}xeTccnc%oacv=_PkmEIbBoNkJW!VvP
zndf*-THXXHgeWK&QrrE$`7t!<AVdwW8@x5RYOv5?iNR~;$;?}s_cHHf-o<=|L4o-I
z^FikQ%*UA5Gw+70$CrvAkp}V}$mJj}gWL`B63mOpft|{b!{@{&&8N)g!Y9wC%I5>K
z<3ED}pE#c(p9h~hp9`NfJmwHdTmjkZ44Dj(3_0)s%7YohfNU8kyJW&mDq_fF<YmZa
z$b_p!%~+ufc?_8h<=_lc3{G)*43%h^59%crITk|}PqYMX0ZR5Tv)NqPT-Y2L6xg)b
zEZGd%yg+PNc4kv&b78Y&Q)hEUa@&1S5`h}efa=xMlGF-NwZh<>Ur<?;nVwOiU}Rum
zuHaZukdvz5nU}1mqmY=BqX3pFRwzm>PAw`+P0?cjRiBWO{c{kwT4WSEj}R;IWni#<
z$H2gR0U;Ju%D}Lun1Mmu1R<s&#lRpspMhbAS#oJn8N@sWM#i%Y48>{2MqoZ8j}rp}
z>%IT~KdUh?uuo!O_&f3c|KE%L|Npy~fr0rB1H*wF1|5(}P;-ER>BF49m8ak4&YN?k
zw|n!wJ=J|Rk38}cJC{9Mxb4-;V@f*LS0_A|T6?=j|2Jdop@-e7+#1tQMHL?5Nm%5P
zlJ)bTTw%DTXLME3tk@|L&S6W_!!Ab2=2hRz(x3hM+{OHW>a+I?%{E+j>QLmEF=dDH
z_QZ&a=i4NVD{pj!Mb4<4w({pe%P$;UDpfNtMt;w<YVEpo@@CYhT~=2=OEj9NThG2-
zsBNuev7%k;&unp@YANe$S~UlIB(H1Fw7Pq=Cdc#cb@ATUhpzmo{b(b%ly_0ilrv}Z
pw*7mTsCWKFoP6j0rTfqG*T3)gcM$sWL-b0x;zB1*W-bN>YXFsl48Z^Z

literal 0
HcmV?d00001

diff --git a/data/red_circle.jpg b/data/red_circle.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5af437be485f66ac069cc735fe2e2a76d0908a64
GIT binary patch
literal 3779
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

literal 0
HcmV?d00001

diff --git a/data/red_circle.png b/data/red_circle.png
new file mode 100644
index 0000000000000000000000000000000000000000..908c1242aa2c135b1123925572820d65ae8d2ae7
GIT binary patch
literal 3919
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
Mp00i_>zopr0LU<8kN^Mx

literal 0
HcmV?d00001

diff --git a/effects/focus.js b/effects/focus.js
new file mode 100644
index 0000000..02a3525
--- /dev/null
+++ b/effects/focus.js
@@ -0,0 +1,117 @@
+var focus={}
+
+focus.MovingFocus = function(opt_options) {
+  this.focus_x=opt_options.focus_x;
+  this.focus_y=opt_options.focus_y;
+  this.focus_radius=opt_options.focus_radius;
+  this.focus_dx=opt_options&&opt_options.focus_dx?opt_options.focus_dx:this.focus_radius/10;
+  this.focus_dy=opt_options&&opt_options.focus_dy?opt_options.focus_dy:this.focus_radius/10;
+  this.width=opt_options.width;
+  this.height=opt_options.height;
+  this.random=opt_options&&opt_options.random?opt_options.random:false;
+}
+
+focus.MovingFocus.prototype.updateFocus=function() {
+  var increment_x=Math.round(this.focus_dx*(this.random?Math.random():1))
+  this.focus_x+=increment_x;
+
+  if ((this.focus_x+this.focus_radius>this.width)
+          || (this.focus_x-this.focus_radius<0)) {
+      this.focus_dx=-this.focus_dx;
+      this.focus_x+=2*this.focus_dx;
+
+  }
+
+  var increment_y=Math.round(this.focus_dy*(this.random?Math.random():1))
+  this.focus_y+=increment_y;
+  if ((this.focus_y+this.focus_radius>this.height)
+  ||(this.focus_y-this.focus_radius<0))
+  {
+      this.focus_dy=-this.focus_dy;
+      this.focus_y+=2*this.focus_dy;
+
+  }
+}
+
+focus.MovingFocus.prototype.isInsideFocus=function(i,j) {
+    if (Math.sqrt((i-this.focus_x)*(i-this.focus_x)+
+            (j-this.focus_y)*(j-this.focus_y))
+            <=this.focus_radius)
+        return true;
+    else
+        return false;
+}
+
+focus.MovingFocus.prototype.process = function(in_imgData,out_imgData) {
+  var self=this;
+  var w=0;
+  var count=0;
+  var pixels=in_imgData.data;
+
+  //console.log("track "+this.focus_x+"x"+this.focus_y);
+  for (var i = 0; i < in_imgData.height; i++) {
+    for (var j = 0; j < in_imgData.width; j++) {
+      var mean=(pixels[w+1]+pixels[w+2]+pixels[w])/3;
+
+      if (this.isInsideFocus(j,i)) {
+          var focus_i=i-this.focus_y;
+          var focus_j=j-this.focus_x;
+          var angleF=Math.atan2(focus_i,focus_j);
+
+          var radius=Math.round(Math.sqrt(focus_i*focus_i+focus_j*focus_j));
+          var angleR=Math.atan2(radius,this.focus_radius);
+
+          var cible_i=Math.round(this.focus_y+focus_i);
+          var cible_j=Math.round(this.focus_x+focus_j);
+
+          //var cible_i=Math.round(this.focus_y+
+          //                            radius*Math.sin(-angle));
+          //var cible_j=Math.round(this.focus_x+
+          //                             radius*Math.cos(-angle));
+
+          //var cible_i=Math.round(this.focus_y+
+          //                            radius*Math.sin(angle)*Math.sin(angle));
+          //var cible_j=Math.round(this.focus_x+
+          //                             radius*Math.cos(angle)*Math.cos(angle));
+
+          /*var cible_i=Math.round(this.focus_y+
+                                      radius*(Math.sin(angle*angle)));
+          var cible_j=Math.round(this.focus_x+
+                                       radius*(Math.cos(angle*angle)));
+          */
+
+          /*
+          var cible_i=Math.round(this.focus_y+
+                                      radius*(Math.sin(angleF)*Math.sin(angleR)));
+          var cible_j=Math.round(this.focus_x+
+                                       radius*(Math.cos(angleF)*Math.cos(angleR)));
+          */
+
+          /*var cible_i=Math.round(this.focus_y+
+                                      radius*(Math.sin(angleF)/Math.sin(angleR)));
+          var cible_j=Math.round(this.focus_x+
+                                       radius*(Math.cos(angleF)/Math.cos(angleR)));
+*/
+
+          /*var cible_i=Math.round(this.focus_y+
+                                      radius*radius/this.focus_radius
+                                      *Math.sin(angleF));
+          var cible_j=Math.round(this.focus_x+
+                                       radius*radius/this.focus_radius
+                                       *Math.cos(angleF));*/
+
+          var cible_w=(cible_i*in_imgData.width+cible_j)<<2;
+          //console.log(i+"x"+j+" "+w+" "+focus_i+"x"+focus_j+" - "+cible_i+"x"+cible_j+ " "+cible_w);
+          out_imgData.data[w+1]=pixels[cible_w+1];
+          out_imgData.data[w+2]=pixels[cible_w+2];
+          out_imgData.data[w]=pixels[cible_w];
+      } else {
+          out_imgData.data[w + 1]=mean * 1;
+          out_imgData.data[w + 2]=mean * 1;
+          out_imgData.data[ w ]=mean * 1;
+      }
+      out_imgData.data[w+3]=pixels[w+3];
+      w+=4;
+    }
+  }
+};
diff --git a/effects/generic.js b/effects/generic.js
new file mode 100644
index 0000000..c6d3fd9
--- /dev/null
+++ b/effects/generic.js
@@ -0,0 +1,19 @@
+var generic_effect={}
+
+generic_effect.apply_region_filter=function(in_imgData,out_imgData,window_width,window_height,
+  pixel_form_region_rgba_filter) {
+
+  var w=0;
+
+  for (var y=0;y<out_imgData.height;y++)
+    for (var x=0;x<out_imgData.width;x++) {
+      w=((y*out_imgData.width)+x)<<2;
+      new_rgba_pixel_val=pixel_form_region_rgba_filter(
+          in_imgData,
+          x-Math.round(window_width/2), y-Math.round(window_height/2),
+          window_width, window_height);
+      for (var i=0;i<4;i++) {
+        out_imgData.data[w+i]=new_rgba_pixel_val[i];
+      }
+    }
+}
diff --git a/effects/morpho.js b/effects/morpho.js
new file mode 100644
index 0000000..9a8fcb8
--- /dev/null
+++ b/effects/morpho.js
@@ -0,0 +1,42 @@
+var morpho_effects={}
+
+morpho_effects.DilatationWindow = function(opt_options) {
+  this.window_width=opt_options.window_width;
+  this.window_height=opt_options.window_height;
+}
+
+morpho_effects.DilatationWindow.prototype.process=function(in_imgData,out_imgData) {
+  return generic_effect.apply_region_filter(
+      in_imgData,out_imgData,this.window_width,this.window_height,
+      morpho_filters.max_from_region
+    );
+}
+
+morpho_effects.ErosionWindow = function(opt_options) {
+  this.window_width=opt_options.window_width;
+  this.window_height=opt_options.window_height;
+}
+
+morpho_effects.ErosionWindow.prototype.process=function(in_imgData,out_imgData) {
+  generic_effect.apply_region_filter(
+      in_imgData,out_imgData,this.window_width,this.window_height,
+      morpho_filters.min_from_region);
+}
+
+morpho_effects.DilatationErosionWindow = function(opt_options) {
+  this.dilatation_width=opt_options.dilatation_width;
+  this.dilatation_height=opt_options.dilatation_height;
+  this.erosion_width=opt_options.erosion_width;
+  this.erosion_height=opt_options.erosion_height;
+  this.aux_cvs=document.createElement("canvas");
+}
+
+morpho_effects.DilatationErosionWindow.prototype.process=function(in_imgData,out_imgData) {
+  aux_imgData ={data:[],width:out_imgData.width,height:out_imgData.height};
+  generic_effect.apply_region_filter(
+      in_imgData,aux_imgData,this.dilatation_width,this.dilatation_height,
+      morpho_filters.max_from_region);
+  generic_effect.apply_region_filter(
+      aux_imgData,out_imgData,this.erosion_width,this.erosion_height,
+      morpho_filters.min_from_region);
+}
diff --git a/features/pixels.js b/features/pixels.js
index d471e62..973c663 100644
--- a/features/pixels.js
+++ b/features/pixels.js
@@ -25,7 +25,7 @@ pixels_features.mean_rgb_per_region=function(imageData,opt_options) {
   var mean=[];
   mean[0]=0; mean[1]=0; mean[2]=0;
   var pos=0; var count=0;
-  for (var y=y0;y<y0+dy;y++)
+  for (var y=y0;y<y0+dy;y++) {
     for (var x=x0;x<x0+dx;x++) {
       pos=(y*imageData.width+x)<<2;
       if (imageData.data[pos+3]>0) {
@@ -35,11 +35,15 @@ pixels_features.mean_rgb_per_region=function(imageData,opt_options) {
         count++;
       }
     }
+  }
   if (count>0) {
+    console.log("count: "+count);
     for (var i=0;i<3;i++) {
       mean[i]=Math.round(mean[i]/count);
     }
     return mean;
+  } else {
+    console.log("no positive Alpha, no rgb mean");
   }
   return undefined;
 }
@@ -53,3 +57,38 @@ pixels_features.mean_rgb_per_region=function(imageData,opt_options) {
 pixels_features.grid_mean_rgb=function(imageData,opt_options) {
   return generic_features.grid_descriptor(imageData,pixels_features.mean_rgb_per_region,opt_options);
 }
+
+/*
+  pixels_features.mean_rgb_afactor_per_region
+  - computes RGB mean of all pixels considering A as a weight of RGB channels
+  within opt_options.x0 .y0 .dx .dy
+  - if opt_options missing partially,
+      replace partially with defaults 0, 0, imageData.width, imageData.height
+  - returns undefined if none available
+*/
+pixels_features.mean_rgb_afactor_per_region=function(imageData,opt_options) {
+  x0=opt_options&&opt_options.x0?opt_options.x0:0;
+  y0=opt_options&&opt_options.y0?opt_options.y0:0;
+  dx=opt_options&&opt_options.dx?opt_options.dx:imageData.width;
+  dy=opt_options&&opt_options.dy?opt_options.dy:imageData.height;
+
+
+  var mean=[];
+  mean[0]=0; mean[1]=0; mean[2]=0;
+  var pos=0; var count=0;
+  for (var y=y0;y<y0+dy;y++)
+    for (var x=x0;x<x0+dx;x++) {
+      pos=(y*imageData.width+x)<<2;
+      for (var i=0;i<3;i++) {
+        mean[i]+=(imageData.data[pos+i]*imageData.data[pos+3]);
+      }
+      count++;
+    }
+  if (count>0) {
+    for (var i=0;i<3;i++) {
+      mean[i]=Math.round(mean[i]/count);
+    }
+    return mean;
+  }
+  return undefined;
+}
diff --git a/filters/linear.js b/filters/linear.js
new file mode 100644
index 0000000..7747d6d
--- /dev/null
+++ b/filters/linear.js
@@ -0,0 +1,6 @@
+var linear_filters={}
+
+linear_filters.identity=function(imgData,x0,y0,region_width,region_height) {
+  var w=((y0+Math.round(region_height/2))*imgData.width+(x0+Math.round(region_width/2)))<<2;
+  return [imgData.data[w],imgData.data[w+1],imgData.data[w+2],imgData.data[w+3]];
+}
diff --git a/filters/morpho.js b/filters/morpho.js
new file mode 100644
index 0000000..7b62205
--- /dev/null
+++ b/filters/morpho.js
@@ -0,0 +1,84 @@
+var morpho_filters={}
+
+morpho_filters.max_from_region=function(imgData,x0,y0,reg_width,reg_height) {
+  var pixels=imgData.data;
+  var w=((y0+Math.round(reg_height/2))*imgData.width
+                                        +(x0+Math.round(reg_width/2)))<<2;
+  var max_data=[pixels[w],pixels[w+1],pixels[w+2],pixels[w+3]];
+  var max=(pixels[w]+pixels[w+1]+pixels[w+2])*pixels[w+3];
+
+  for (var y=Math.max(0,y0);y<Math.min(y0+reg_height,imgData.height);y++)
+    for (var x=Math.max(0,x0);x<Math.min(x0+reg_width,imgData.width);x++) {
+      w = (y*imgData.width+x)<<2;
+      var val = (pixels[w]+pixels[w+1]+pixels[w+2])*pixels[w+3];
+      if (max < val) {
+        if (pixels[w+3]<255) pixels[w+3]=255;
+        max = val; max_data = [pixels[w],pixels[w+1],pixels[w+2],pixels[w+3]];
+      }
+    }
+  return max_data;
+}
+
+morpho_filters.max_red_from_region=function(imgData,x0,y0,reg_width,reg_height) {
+  var pixels=imgData.data;
+  var w=((y0+Math.round(reg_height/2))*imgData.width+(x0+Math.round(reg_width/2)))<<2;
+  var max_data=[pixels[w],pixels[w+1],pixels[w+2],pixels[w+3]];
+  var max=(pixels[w]-pixels[w+1]-pixels[w+2])*pixels[w+3];
+  for (var y=y0;y<y0+reg_height;y++) {
+    if (y<0 || y>imgData.height) continue;
+    for (var x=x0;x<x0+reg_width;x++) {
+      if (x<0 || x>imgData.width) continue;
+      w = (y*imgData.width+x)<<2;
+      var val = (pixels[w]-pixels[w+1]-pixels[w+2])*pixels[w+3];
+      if (max < val) {
+        max = val;
+        max_data=[pixels[w], pixels[w+1],
+                  pixels[w+2], pixels[w+3]];
+      }
+    }
+  }
+  return max_data;
+}
+
+morpho_filters.max_gray_from_region=function(imgData,x0,y0,reg_width,reg_height) {
+  var pixels=imgData.data;
+  var w=((y0+Math.round(reg_height/2))*imgData.width+(x0+Math.round(reg_width/2)))<<2;
+  var max_data=[pixels[w],pixels[w+1],pixels[w+2],pixels[w+3]];
+  var max=(pixels[w]+pixels[w+1]+pixels[w+2])*pixels[w+3];
+
+  for (var y=y0;y<y0+reg_height;y++) {
+    if (y<0 || y>imgData.height) continue;
+    for (var x=x0;x<x0+reg_width;x++) {
+      if (x<0 || x>imgData.width) continue;
+      w = (y*imgData.width+x)<<2;
+      var val = (pixels[w]+pixels[w+1]+pixels[w+2])*pixels[w+3];
+      if (max < val) {
+        max = val;
+        max_data=[val/3, val/3,
+                  val/3, pixels[w+3]];
+      }
+    }
+  }
+  return max_data;
+}
+
+morpho_filters.min_from_region=function(imgData,x0,y0,reg_width,reg_height) {
+  var pixels=imgData.data;
+  var w=((y0+Math.round(reg_height/2))*imgData.width+(x0+Math.round(reg_width/2)))<<2;
+  var min_data=[pixels[w],pixels[w+1],pixels[w+2],pixels[w+3]];
+  var min=(pixels[w]+pixels[w+1]+pixels[w+2])*pixels[w+3];
+  for (var y=y0;y<y0+reg_height;y++) {
+    if (y<0 || y>imgData.height) continue;
+    for (var x=x0;x<x0+reg_width;x++) {
+      if (x<0 || x>imgData.width) continue;
+      w = (y*imgData.width+x)<<2;
+      var val = (pixels[w]+pixels[w+1]+pixels[w+2])*pixels[w+3];
+      if (min > val) {
+        min = val;
+        min_data=[pixels[w], pixels[w+1],
+                  pixels[w+2], pixels[w+3]];
+      }
+    }
+  }
+  return min_data;
+}
diff --git a/metrics/pixels.js b/metrics/pixels.js
index 2a9441c..954dd6c 100644
--- a/metrics/pixels.js
+++ b/metrics/pixels.js
@@ -9,10 +9,42 @@ pixel_metrics.rgb_edist=function(pixel_rgb1, pixel_rgb2) {
 }
 
 /*
-    pixel_metrics.rgb_edist - computes euclidian distance between two grids
+    pixel_metrics.grid_rgb_edist - computes euclidian distance between two grids
     containing in each cell an rgb pixel
 */
 pixel_metrics.grid_rgb_edist=function(pixels_rgb_grid1, pixels_rgb_grid2) {
   var dist_fun=pixel_metrics.rgb_edist;
   return generic_metrics.euclidian_distance_btw_feature_vectors(pixels_rgb_grid1.cells,pixels_rgb_grid2.cells,dist_fun);
 }
+
+/*
+    pixel_metrics.pixel_edist - computes euclidian distance between two pixels
+    regardless of color encoding
+*/
+pixel_metrics.pixel_edist=function(pixel_rgb1, pixel_rgb2) {
+  var dist_fun=function(x,y){return x-y};
+  return generic_metrics.euclidian_distance_btw_feature_vectors(pixel_rgb1,pixel_rgb2,dist_fun);
+}
+
+/*
+    pixel_metrics.gray_edist - computes euclidian distance between two pixels
+    considering the intensity value
+*/
+pixel_metrics.gray_edist=function(pixel_rgb1, pixel_rgb2) {
+  var pixel_gray_1=Math.round((pixel_rgb1[0]+pixel_rgb1[1]+pixel_rgb1[2])/3);
+  var pixel_gray_2=Math.round((pixel_rgb2[0]+pixel_rgb2[1]+pixel_rgb2[2])/3);
+
+  return Math.abs(pixel_gray_1-pixel_gray_2);
+}
+
+/*
+    pixel_metrics.visible_edist - computes euclidian distance between two pixels
+    considering the fact that pixels are both visible/invisible
+    returns 0 or 1
+*/
+pixel_metrics.visible_edist=function(pixel_rgb1, pixel_rgb2) {
+  var pixel_gray_1=Math.round((pixel_rgb1[0]+pixel_rgb1[1]+pixel_rgb1[2])/3);
+  var pixel_gray_2=Math.round((pixel_rgb2[0]+pixel_rgb2[1]+pixel_rgb2[2])/3);
+  return (pixel_gray_1>0 && pixel_gray_2>0) || (pixel_gray_1==0 && pixel_gray_2==0);
+
+}
diff --git a/movement/detection.js b/movement/detection.js
new file mode 100644
index 0000000..d264ba9
--- /dev/null
+++ b/movement/detection.js
@@ -0,0 +1,55 @@
+var mvt_detection={}
+
+mvt_detection.SimpleMvtDetectorRegion=function(opt_options) {
+  this.difference = document.createElement("canvas");
+  this.previous = document.createElement("canvas");
+
+  this.bbox=opt_options.bbox;
+
+  this.difference.width=this.bbox.dx; this.difference.height=this.bbox.dy;
+  this.previous.width=this.bbox.dx; this.previous.height=this.bbox.dy;
+
+  this.difference_ctxt = this.difference.getContext("2d");
+  this.previous_ctxt = this.previous.getContext("2d");
+
+  this.diff_threshold=opt_options.diff_threshold;
+  this.mvt_threshold=opt_options.mvt_threshold;
+}
+
+mvt_detection.SimpleMvtDetectorRegion.prototype.set_first_frame_from_eltId=function(eltId) {
+  var elt=document.getElementById(eltId);
+  var cvs=document.createElement("canvas");
+  cvs.width=elt.width; cvs.height=elt.height;
+  var ctxt=cvs.getContext("2d");
+  ctxt.drawImage(elt,0,0);
+  var first_imgData=ctxt.getImageData(this.bbox.x0,this.bbox.y0,this.bbox.dx,this.bbox.dy);
+  this.previous_ctxt.putImageData(first_imgData,0,0);
+}
+
+mvt_detection.SimpleMvtDetectorRegion.prototype.process=function(in_imgData, out_imgData) {
+  var previous_imgData = this.previous_ctxt.
+                            getImageData(0, 0, this.bbox.dx, this.bbox.dy);
+  in_reg_imgData=in_imgData.ctxt.getImageData(this.bbox.x0,this.bbox.y0,this.bbox.dx,this.bbox.dy);
+
+  var w=0;
+  var count=0;
+  var min=[255,255,255], max=[0,0,0];
+  for (var x=0; x<this.bbox.dx ; x++)
+    for (var y=0; y<this.bbox.dy ; y++) {
+      var diff=0;
+        for (var i=0; i<3; i++) {
+          out_imgData.data[w+i] = Math.abs(in_reg_imgData.data[w+i] - previous_imgData.data[w+i]);
+          diff += out_imgData.data[w+i];
+        }
+        count+=(diff>this.diff_threshold);
+        out_imgData.data[w+3] = 255;
+        w+=4;
+    }
+
+  this.previous_ctxt.putImageData(in_reg_imgData,0,0);
+  if (out_imgData) {
+    Tools.copy_partial_imageData_into_imageData(in_imgData,0,0,in_imgData.width,in_imgData.height,out_imgData,0,0);
+    Tools.strokeBBox_on_imageData(out_imgData,this.bbox,[255*(count>this.mvt_threshold),0,0,255]);
+  }
+  return (count>this.mvt_threshold);
+}
diff --git a/movement/difference.js b/movement/difference.js
new file mode 100644
index 0000000..4664169
--- /dev/null
+++ b/movement/difference.js
@@ -0,0 +1,77 @@
+var diff={}
+
+diff.DifferenceImageRGB=function(opt_options) {
+  this.difference = document.createElement("canvas");
+  this.previous = document.createElement("canvas");
+
+  this.width=opt_options.width;
+  this.height=opt_options.height;
+
+  this.difference.width=this.width; this.difference.height=this.height;
+  this.previous.width=this.width; this.previous.height=this.height;
+
+  this.difference_ctxt = this.difference.getContext("2d");
+  this.previous_ctxt = this.previous.getContext("2d");
+}
+
+diff.DifferenceImageRGB.prototype.set_first_frame_imgData=function(imgData) {
+  this.previous_ctxt.putImageData(imgData,0,0);
+}
+
+diff.DifferenceImageRGB.prototype.set_first_frame_from_eltId=function(eltId) {
+  var elt=document.getElementById(eltId);
+  var cvs=document.createElement("canvas");
+  cvs.width=elt.width; cvs.height=elt.height;
+  var ctxt=cvs.getContext("2d");
+  ctxt.drawImage(elt,0,0);
+  this.set_first_frame_imgData(ctxt.getImageData(0,0,cvs.width,cvs.height));
+}
+
+diff.DifferenceImageRGB.prototype.process=function(in_imgData, out_imgData) {
+  var previous_imgData = this.previous_ctxt.
+                            getImageData(0, 0, this.width, this.height);
+  var w=0;
+  var min=[255,255,255], max=[0,0,0];
+  for (var x=0; x<this.width ; x++)
+    for (var y=0; y<this.height ; y++) {
+        for (var i=0; i<3; i++) {
+          out_imgData.data[w+i] =
+          Math.abs(in_imgData.data[w+i] - previous_imgData.data[w+i]);
+        }
+        out_imgData.data[w+3] = 255;
+        w+=4;
+    }
+  this.previous_ctxt.putImageData(in_imgData,0,0);
+}
+
+diff.NormalizedDifferenceImageRGB=function(opt_options) {
+  this.__proto__.__proto__=new diff.DifferenceImageRGB(opt_options);
+}
+
+diff.NormalizedDifferenceImageRGB.prototype.process=function(in_imgData, out_imgData) {
+  var previous_imgData = this.previous_ctxt.
+                            getImageData(0, 0, this.width, this.height);
+  var w=0;
+  var min=[255,255,255], max=[0,0,0];
+  for (var x=0; x<this.width ; x++)
+    for (var y=0; y<this.height ; y++) {
+        for (var i=0; i<3; i++) {
+          out_imgData.data[w+i] = Math.abs(in_imgData.data[w+i] - previous_imgData.data[w+i]);
+          if (min[i]>out_imgData.data[w+i]) min[i]=out_imgData.data[w+i];
+          if (max[i]<out_imgData.data[w+i]) max[i]=out_imgData.data[w+i];
+        }
+        out_imgData.data[w+3] = 255;
+        w+=4;
+    }
+
+  w=0;
+  for (var x=0; x<this.width ; x++)
+    for (var y=0; y<this.height ; y++) {
+        for (var i=0; i<3; i++) {
+          out_imgData.data[w+i] = (out_imgData.data[w+i]-min[i])*255/(max[i]-min[i]);
+        }
+        out_imgData.data[w+3] = 255;
+        w+=4;
+    }
+  this.previous_ctxt.putImageData(in_imgData,0,0);
+}
diff --git a/processing.js b/processing.js
index 0f262d9..675e365 100644
--- a/processing.js
+++ b/processing.js
@@ -5,11 +5,14 @@ var processing=function(elementId,task,outputCanvasId) {
 
   this.imageData = {};
 
-  this.processing_canvas=document.createElement('canvas');
-  this.processing_canvas.width = this.width;
-  this.processing_canvas.height = this.height;
-
-  this.processing_context=this.processing_canvas.getContext("2d");
+  if (this.element.nodeName.toLowerCase()!="canvas") {
+    this.processing_canvas=document.createElement('canvas');
+    this.processing_canvas.width = this.width;
+    this.processing_canvas.height = this.height;
+    this.processing_context=this.processing_canvas.getContext("2d");
+  } else {
+    this.processing_canvas=this.element;
+  }
 
   this.task=task;
 
@@ -30,8 +33,7 @@ processing.prototype.acquire_data_from_video=function() {
 }
 
 processing.prototype.acquire_data_from_canvas=function() {
-  this.processing_context.drawImage(this.element,0,0,this.width,this.height);
-  this.imageData = this.processing_context.getImageData(0, 0, this.width, this.height);
+  this.imageData = this.this.processing_canvas.getContext("2d").getImageData(0, 0, this.width, this.height);
 }
 
 processing.prototype.acquire_data=function() {
diff --git a/processing2.js b/processing2.js
index eba599f..0daf792 100644
--- a/processing2.js
+++ b/processing2.js
@@ -8,12 +8,15 @@ var processing2=function(elementId,task,outputCanvasId,opt_options) {
   this.in_imageData = {};
   this.out_imageData = {};
 
-  this.processing_canvas=document.createElement('canvas');
-  this.processing_canvas.width = this.width;
-  this.processing_canvas.height = this.height;
+  if (this.element.nodeName.toLowerCase()!="canvas") {
+    this.processing_canvas=document.createElement('canvas');
+    this.processing_canvas.width = this.width;
+    this.processing_canvas.height = this.height;
+  } else {
+    this.processing_canvas=this.element;
+  }
 
   this.processing_context=this.processing_canvas.getContext("2d");
-
   this.task=task;
 
 
@@ -46,13 +49,14 @@ processing2.prototype.acquire_data_from_video=function() {
 }
 
 processing2.prototype.acquire_data_from_canvas=function() {
-  this.processing_context.drawImage(this.element,0,0,this.width,this.height);
   this.in_imageData = this.processing_context.getImageData(this.in_region.x, this.in_region.y, this.in_region.width, this.in_region.height);
 }
 
 processing2.prototype.acquire_data=function() {
-  if (this.output_canvas)
+  if (this.output_canvas) {
     this.out_imageData=this.output_context.getImageData(this.out_region.x,this.out_region.y,this.out_region.width,this.out_region.height);
+    this.out_imageData.ctxt=this.output_context;
+  }
 
   switch (this.element.nodeName.toLowerCase()) {
       case 'canvas':
@@ -64,6 +68,7 @@ processing2.prototype.acquire_data=function() {
       default:
         throw new Error('Element not supported!');
     }
+    this.in_imageData.ctxt = this.processing_context;
 }
 
 processing2.prototype.do_process=function() {
diff --git a/samples/blobs_appearance.html b/samples/blobs_appearance.html
new file mode 100644
index 0000000..5db9f99
--- /dev/null
+++ b/samples/blobs_appearance.html
@@ -0,0 +1,38 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../segmentation/blobs.js"></script>
+</head>
+<body>
+
+  <img id="input" src="../data/rgb.png" width="50" height="50"></img>
+  <canvas id="output" width="50" height="50"></canvas>
+
+
+
+  <script lang="javascript">
+    var _opt_options={metric:pixel_metrics.gray_edist,threshold:10};
+    var _task1=new blobs.Pixel8ConnectivityBlobs(_opt_options);
+    var _proc1=new processing2("input",_task1,"output");
+    _proc1.do_process();
+    var components_bbox=_proc1.get_result();
+
+    /*
+    var cvs_ctxt=document.getElementById("output").getContext("2d");
+    console.log(components_bbox);
+    for (idx in components_bbox) {
+      cvs_ctxt.strokeStyle=[components_bbox[idx].mean[0],
+      components_bbox[idx].mean[1],components_bbox[idx].mean[2],255];
+      cvs_ctxt.strokeRect(
+          components_bbox[idx].x, components_bbox[idx].y,
+          components_bbox[idx].width, components_bbox[idx].height,
+          );
+    }*/
+  </script>
+</body>
+</html>
diff --git a/samples/blobs_diff_img_dilatation_erosion.html b/samples/blobs_diff_img_dilatation_erosion.html
new file mode 100644
index 0000000..bf7ae56
--- /dev/null
+++ b/samples/blobs_diff_img_dilatation_erosion.html
@@ -0,0 +1,71 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../effects/generic.js"></script>
+<script lang="js" src="../effects/morpho.js"></script>
+<script lang="js" src="../filters/morpho.js"></script>
+<script lang="js" src="../filters/linear.js"></script>
+
+<script lang="js" src="../segmentation/blobs.js"></script>
+</head>
+<body>
+
+  <img id="input" src="../data/16_03.jpg" width="320" height="180"></img>
+  <canvas id="output1" width="320" height="180"></canvas><br></br>
+  <canvas id="output2" width="320" height="180" style="display:none"></canvas>
+  <canvas id="output3" width="320" height="180"></canvas>
+  <canvas id="output4" width="320" height="180"></canvas>
+
+
+  <script lang="javascript">
+    var _focus_options={focus_x:50,focus_y:50,focus_dx:5,focus_dy:5,focus_radius:50,width:320,height:180};
+    var _task1=new focus.MovingFocus(_focus_options);
+    var _proc1=new processing2("input",_task1,"output1");
+
+    var _opt_options={width:320,height:180};
+    var _task2=new diff.NormalizedDifferenceImageRGB(_opt_options);
+    var _proc2=new processing2("output1",_task2,"output2");
+
+    var _opt_options={dilatation_width:8,dilatation_height:8,
+                      erosion_width:2,erosion_height:2};
+    var _task3=new morpho_effects.DilatationErosionWindow(_opt_options);
+    var _proc3=new processing2("output2",_task3,"output3");
+
+    var _opt_options={metric:pixel_metrics.visible_edist,threshold:0};
+    var _task4=new blobs.Pixel8ConnectivityBlobs(_opt_options);
+    var _proc4=new processing2("output3",_task4,"output4");
+
+    var img_elt=document.getElementById("input");
+    img_elt.addEventListener("loadeddata",function(){
+      _task2.set_first_frame_from_eltId("input");
+    });
+    var count=0;
+    var cvs4=document.getElementById("output4");
+    var cvs4_ctxt=cvs4.getContext("2d");
+    var loop=function() {
+      _proc1.do_process();
+      _task1.updateFocus();
+
+      _proc2.do_process();
+      _proc3.do_process();
+
+      cvs4_ctxt.beginPath();
+      cvs4_ctxt.clearRect(0,0,cvs4.width,cvs4.height);
+      _proc4.do_process();
+
+      count++;
+
+      if (count<1000)
+        setTimeout(loop,200);
+    }
+
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/samples/blobs_diff_img_no_normalization.html b/samples/blobs_diff_img_no_normalization.html
new file mode 100644
index 0000000..d39b7bd
--- /dev/null
+++ b/samples/blobs_diff_img_no_normalization.html
@@ -0,0 +1,61 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../movement/detection.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../segmentation/blobs.js"></script>
+</head>
+<body>
+
+  <img id="input" src="../data/16_03.jpg" width="320" height="180"></img>
+  <canvas id="output1" width="320" height="180"></canvas>
+  <canvas id="output2" width="320" height="180"></canvas>
+  <canvas id="output3" width="320" height="180"></canvas>
+
+
+  <script lang="javascript">
+    var _focus_options={focus_x:20,focus_y:20,focus_radius:20,width:320,height:180};
+    var _task1=new focus.MovingFocus(_focus_options);
+    var _proc1=new processing2("input",_task1,"output1");
+
+    var _opt_options={width:320,height:180};
+    var _task2=new diff.DifferenceImageRGB(_opt_options);
+    var _proc2=new processing2("output1",_task2,"output2");
+
+    var _opt_options={metric:pixel_metrics.visible_edist,threshold:0};
+    var _task3=new blobs.Pixel8ConnectivityBlobs(_opt_options);
+    var _proc3=new processing2("output2",_task3,"output3");
+
+    var img_elt=document.getElementById("input");
+    img_elt.addEventListener("loadeddata",function(){
+      _task2.set_first_frame_from_eltId("input");
+    });
+
+    var cvs3=document.getElementById("output3");
+    var cvs3_ctxt=cvs3.getContext("2d");
+
+    var count=0;
+    var loop=function() {
+      _proc1.do_process();
+      _task1.updateFocus();
+
+      _proc2.do_process();
+
+      cvs3_ctxt.beginPath();
+      cvs3_ctxt.clearRect(0,0,cvs3.width,cvs3.height);
+      _proc3.do_process();
+      count++;
+
+      if (count<100)
+        setTimeout(loop,100);
+    }
+
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/samples/blobs_diff_img_with_normalization.html b/samples/blobs_diff_img_with_normalization.html
new file mode 100644
index 0000000..8fd0661
--- /dev/null
+++ b/samples/blobs_diff_img_with_normalization.html
@@ -0,0 +1,61 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../segmentation/blobs.js"></script>
+</head>
+<body>
+
+  <img id="input" src="../data/16_03.jpg" width="320" height="180"></img>
+  <canvas id="output1" width="320" height="180"></canvas><br></br>
+  <canvas id="output2" width="320" height="180"></canvas>
+  <canvas id="output3" width="320" height="180"></canvas>
+
+
+
+  <script lang="javascript">
+    var _focus_options={focus_x:50,focus_y:50,focus_radius:50,width:320,height:180};
+    var _task1=new focus.MovingFocus(_focus_options);
+    var _proc1=new processing2("input",_task1,"output1");
+
+    var _opt_options={width:320,height:180};
+    var _task2=new diff.NormalizedDifferenceImageRGB(_opt_options);
+    var _proc2=new processing2("output1",_task2,"output2");
+
+    var _opt_options={metric:pixel_metrics.visible_edist,threshold:0};
+    var _task3=new blobs.Pixel8ConnectivityBlobs(_opt_options);
+    var _proc3=new processing2("output2",_task3,"output3");
+
+    var img_elt=document.getElementById("input");
+    img_elt.addEventListener("loadeddata",function(){
+      _task2.set_first_frame_from_eltId("input");
+    });
+
+    var cvs3=document.getElementById("output3");
+    var cvs3_ctxt=cvs3.getContext("2d");
+
+    var count=0;
+    var loop=function() {
+      _proc1.do_process();
+      _task1.updateFocus();
+
+      _proc2.do_process();
+      
+      cvs3_ctxt.beginPath();
+      cvs3_ctxt.clearRect(0,0,cvs3.width,cvs3.height);
+      _proc3.do_process();
+      count++;
+      console.log(count);
+      if (count<100)
+        setTimeout(loop,200);
+    }
+
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/samples/difference_animation.html b/samples/difference_animation.html
new file mode 100644
index 0000000..c42fcd1
--- /dev/null
+++ b/samples/difference_animation.html
@@ -0,0 +1,43 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+<script lang="js" src="../features/generic.js"></script>
+<script lang="js" src="../features/pixels.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../animation/moving_circle.js"></script>
+<script lang="js" src="../tracking/appearance.js"></script>
+</head>
+<body><!--style="background-color:black; vertical-align: top;"-->
+
+  <canvas id="input" width="300" height="200"  style="vertical-align: top;"></canvas>
+  <canvas id="output" width="300" height="200"  style="vertical-align: top;"></canvas>
+
+
+  <script lang="javascript">
+    var _opt_options={
+      x0:40,y0:40,radius:40,step_x:10,step_y:10,random:true,
+    };
+    var _task1=new animations.MovingCircle("input",_opt_options);
+
+    var _opt_options={width:300,height:200,threshold:1};
+    var _task2=new diff.NormalizedDifferenceImageRGB(_opt_options);
+    var _proc2=new processing2("input",_task2,"output");
+
+    _task1.draw();
+    _task2.set_first_frame_from_eltId("input");
+
+    var loop=function() {
+      _task1.animate();
+      _proc2.do_process();
+
+      setTimeout(loop,200);
+    }
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/samples/difference_effect.html b/samples/difference_effect.html
new file mode 100644
index 0000000..23e8925
--- /dev/null
+++ b/samples/difference_effect.html
@@ -0,0 +1,47 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+</head>
+<body>
+
+  <img id="input" src="../data/16_05.jpg" width="320" height="180"></img>
+  <canvas id="output1" width="320" height="180"></canvas><br/>
+  <canvas id="output2" width="320" height="180"></canvas>
+  <canvas id="output3" width="320" height="180"></canvas>
+
+
+  <script lang="javascript">
+    var _focus_options={focus_x:50,focus_y:50,focus_radius:50,width:320,height:180,random:true,
+    focus_dx:20,focus_dy:20};
+    var _task1=new focus.MovingFocus(_focus_options);
+    var _proc1=new processing2("input",_task1,"output1");
+
+    var _opt_options={width:320,height:180,threshold:50};
+    var _task2=new diff.DifferenceImageRGB(_opt_options);
+    var _proc2=new processing2("output1",_task2,"output2");
+
+    var _opt_options={width:320,height:180,threshold:1};
+    var _task3=new diff.NormalizedDifferenceImageRGB(_opt_options);
+    var _proc3=new processing2("output1",_task3,"output3");
+
+    var img_elt=document.getElementById("input");
+    img_elt.addEventListener("loadeddata",function(){
+      _task2.set_first_frame_from_eltId("input");
+    });
+    var loop=function() {
+      _proc1.do_process();
+      _task1.updateFocus();
+
+      _proc2.do_process();
+      _proc3.do_process();
+
+      setTimeout(loop,200);
+    }
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/samples/difference_video.html b/samples/difference_video.html
new file mode 100644
index 0000000..bbdbe06
--- /dev/null
+++ b/samples/difference_video.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+
+</head>
+<body>
+  <video autoplay loop src="../data/surprise.mp4" id="input" width="320" height="180"></video>
+  <canvas id="output1" width="320" height="180"></canvas>
+
+  <script lang="javascript">
+    var _opt_options={width:320,height:180};
+
+    var _task1=new diff.DifferenceImageRGB(_opt_options);
+    var video_elt=document.getElementById("input");
+    video_elt.addEventListener("loadeddata",function(){
+      _task1.set_first_frame_from_eltId("input");
+    });
+
+    var _proc1=new processing2("input",_task1,"output1");
+
+    var loop=function() {
+      _proc1.do_process();
+      requestAnimationFrame(loop);
+    }
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/samples/meanshift_changing_circle_and_bg_color.html b/samples/meanshift_changing_circle_and_bg_color.html
new file mode 100644
index 0000000..cf37095
--- /dev/null
+++ b/samples/meanshift_changing_circle_and_bg_color.html
@@ -0,0 +1,66 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+<script lang="js" src="../features/generic.js"></script>
+<script lang="js" src="../features/pixels.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../animation/moving_circle.js"></script>
+<script lang="js" src="../tracking/appearance.js"></script>
+</head>
+<body><!-- style="background-color:black;"-->
+
+  <canvas id="input" width="150" height="100" style="vertical-align:top" ></canvas>
+  <canvas id="output2" width="250" height="400"></canvas>
+
+
+  <script lang="javascript">
+    var _opt_options={
+      x0:10,y0:10,radius:10,step_x:2,step_y:2,random:true,
+      fillColor0:[255,0,0,255],
+      fillColor1:[127,0,0,255],
+      fillColorNbSteps:100,
+      bgColor0:[127,0,0,255],
+      bgColor1:[255,0,0,255],
+      bgColorNbSteps:100
+    };
+    var _task1=new animations.MovingCircleHChangingColorChangingBackground("input",_opt_options);
+
+    var _bbox={x0:0,y0:0,dx:20,dy:20};
+
+    var _tracking_options={
+      window_width:40,
+      window_height:40,
+      step_x:3,
+      step_y:3,
+      update_model:false
+    };
+
+    var _task2=new appearance_tracking.MeanShift(_bbox,
+      pixels_features.mean_rgb_afactor_per_region,
+      pixel_metrics.pixel_edist,
+      _tracking_options);
+
+    var _proc2=new processing2("input",_task2,"output2");
+
+
+    var count=0;
+    _task1.draw();
+    var loop=function() {
+      _proc2.do_process();
+      _task1.animate();
+
+      count++;
+
+      if (count<1000)
+        setTimeout(loop,100);
+    }
+
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/samples/meanshift_changing_circle_color.html b/samples/meanshift_changing_circle_color.html
new file mode 100644
index 0000000..3b8ebfb
--- /dev/null
+++ b/samples/meanshift_changing_circle_color.html
@@ -0,0 +1,62 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+<script lang="js" src="../features/generic.js"></script>
+<script lang="js" src="../features/pixels.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../animation/moving_circle.js"></script>
+<script lang="js" src="../tracking/appearance.js"></script>
+</head>
+<body><!--style="background-color:black; vertical-align: top;"-->
+
+  <canvas id="input" width="150" height="100"  style="vertical-align: top;"></canvas>
+  <canvas id="output2" width="250" height="700" style="vertical-align: top;"></canvas>
+
+
+  <script lang="javascript">
+    var _opt_options={
+      x0:10,y0:10,radius:10,step_x:2,step_y:2,random:true,
+      fillColor0:[255,0,0,255],
+      fillColor1:[0,255,0,255],
+      fillColorNbSteps:100
+    };
+    var _task1=new animations.MovingCircleHChangingColor("input",_opt_options);
+
+    var _bbox={x0:0,y0:0,dx:20,dy:20};
+
+    var _tracking_options={
+      window_width:60,
+      window_height:60,
+      step_x:3,
+      step_y:3,
+      update_model:false
+    };
+
+    var _task2=new appearance_tracking.MeanShift(_bbox,
+      pixels_features.mean_rgb_afactor_per_region,
+      pixel_metrics.pixel_edist,_tracking_options);
+
+    var _proc2=new processing2("input",_task2,"output2");
+
+
+    var count=0;
+    _task1.draw();
+    var loop=function() {
+      _proc2.do_process();
+      _task1.animate();
+
+      count++;
+
+      if (count<1000)
+        setTimeout(loop,100);
+    }
+
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/samples/meanshift_circle.html b/samples/meanshift_circle.html
new file mode 100644
index 0000000..8dbf138
--- /dev/null
+++ b/samples/meanshift_circle.html
@@ -0,0 +1,56 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+<script lang="js" src="../features/generic.js"></script>
+<script lang="js" src="../features/pixels.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../animation/moving_circle.js"></script>
+<script lang="js" src="../tracking/appearance.js"></script>
+</head>
+<body> <!--style="background-color:black;" -->
+
+  <canvas id="input" width="150" height="100" style="vertical-align:top" ></canvas>
+  <canvas id="output2" width="250" height="700"></canvas>
+
+
+  <script lang="javascript">
+    var _opt_options={x0:10,y0:10,radius:10,step_x:2,step_y:2,random:true,fillStyle:"red"};
+    var _task1=new animations.MovingCircle("input",_opt_options);
+
+    var _bbox={x0:0,y0:0,dx:20,dy:20};
+
+    var _tracking_options={
+      window_width:40,
+      window_height:40,
+      step_x:3,
+      step_y:3
+    };
+
+    var _task2=new appearance_tracking.MeanShift(_bbox,
+      pixels_features.mean_rgb_afactor_per_region,
+      pixel_metrics.pixel_edist,_tracking_options);
+
+    var _proc2=new processing2("input",_task2,"output2");
+
+
+    var count=0;
+    _task1.draw();
+    var loop=function() {
+      _proc2.do_process();
+      _task1.animate();
+
+      count++;
+
+      if (count<1000)
+        setTimeout(loop,100);
+    }
+
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/samples/morpho_dilatation.html b/samples/morpho_dilatation.html
new file mode 100644
index 0000000..e69de29
diff --git a/samples/morpho_dilatation_erosion.html b/samples/morpho_dilatation_erosion.html
new file mode 100644
index 0000000..663b1f6
--- /dev/null
+++ b/samples/morpho_dilatation_erosion.html
@@ -0,0 +1,49 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../filters/morpho.js"></script>
+<script lang="js" src="../effects/generic.js"></script>
+<script lang="js" src="../effects/morpho.js"></script>
+</head>
+<body>
+
+  <img id="input" src="../data/red_circle.gif" width="320" height="200"></img>
+  <canvas id="output1"></canvas>
+  <canvas id="output2"></canvas>
+  <br></br>
+  <img id="input2" src="../data/16_03.jpg" width="320" height="200"></img>
+  <canvas id="output3"></canvas>
+  <canvas id="output4"></canvas>
+
+
+
+  <script lang="javascript">
+    Tools.copySize("input","output1");
+    Tools.copySize("input","output2");
+
+    var _opt_options={window_width:10,window_height:10};
+    var _task1=new morpho_effects.DilatationWindow(_opt_options);
+    var _proc1=new processing2("input",_task1,"output1");
+    _proc1.do_process();
+    var _task2=new morpho_effects.ErosionWindow(_opt_options);
+    var _proc2=new processing2("output1",_task2,"output2");
+    _proc2.do_process();
+
+    Tools.copySize("input2","output3");
+    Tools.copySize("input2","output4");
+
+    var _opt_options={window_width:10,window_height:10};
+    var _task3=new morpho_effects.DilatationWindow(_opt_options);
+    var _proc3=new processing2("input2",_task3,"output3");
+    _proc3.do_process();
+    var _task4=new morpho_effects.ErosionWindow(_opt_options);
+    var _proc4=new processing2("output3",_task4,"output4");
+    _proc4.do_process();
+
+  </script>
+</body>
+</html>
diff --git a/samples/mvt_detection_region.html b/samples/mvt_detection_region.html
new file mode 100644
index 0000000..37b331d
--- /dev/null
+++ b/samples/mvt_detection_region.html
@@ -0,0 +1,62 @@
+<html>
+<head>
+<script lang="js" src="../tools.js"></script>
+<script lang="js" src="../processing2.js"></script>
+<script lang="js" src="../movement/difference.js"></script>
+<script lang="js" src="../movement/detection.js"></script>
+<script lang="js" src="../effects/focus.js"></script>
+<script lang="js" src="../metrics/generic.js"></script>
+<script lang="js" src="../metrics/pixels.js"></script>
+<script lang="js" src="../segmentation/blobs.js"></script>
+</head>
+<body>
+
+  <img id="input" src="../data/16_03.jpg" width="320" height="180"></img>
+  <canvas id="output1" width="320" height="180"></canvas><br></br>
+  <canvas id="output2" width="320" height="180"></canvas>
+  <canvas id="output3" width="320" height="180"></canvas>
+
+
+
+  <script lang="javascript">
+    var _focus_options={focus_x:50,focus_y:50,focus_radius:50,width:320,height:180};
+    var _task1=new focus.MovingFocus(_focus_options);
+    var _proc1=new processing2("input",_task1,"output1");
+
+    var _opt_options={width:320,height:180};
+    var _task2=new diff.NormalizedDifferenceImageRGB(_opt_options);
+    var _proc2=new processing2("output1",_task2,"output2");
+
+    var _opt_options={bbox:{x0:100,y0:100,dx:50,dy:50},
+                      mvt_threshold:50,diff_threshold:30};
+    var _task3=new mvt_detection.SimpleMvtDetectorRegion(_opt_options);
+    var _proc3=new processing2("output1",_task3,"output3");
+
+    var img_elt=document.getElementById("input");
+    img_elt.addEventListener("loadeddata",function(){
+      _task2.set_first_frame_from_eltId("input");
+      _task5.set_first_frame_from_eltId("input");
+    });
+
+    var cvs3=document.getElementById("output3");
+    var cvs3_ctxt=cvs3.getContext("2d");
+
+    var count=0;
+    var loop=function() {
+      _proc1.do_process();
+      _task1.updateFocus();
+
+      _proc2.do_process();
+
+      _proc3.do_process();
+
+      count++;
+      if (count<100)
+        setTimeout(loop,100);
+    }
+
+    loop();
+
+  </script>
+</body>
+</html>
diff --git a/segmentation/blobs.js b/segmentation/blobs.js
new file mode 100644
index 0000000..c2eee11
--- /dev/null
+++ b/segmentation/blobs.js
@@ -0,0 +1,93 @@
+var blobs={}
+
+blobs.Pixel8ConnectivityBlobs=function(opt_options) {
+  this.metric = opt_options.metric;
+  this.threshold=opt_options && opt_options.threshold ? opt_options.threshold : 0;
+}
+
+blobs.Pixel8ConnectivityBlobs.prototype.analyse_neighbours=function(in_imgData, x0, y0) {
+  w0=((y0*in_imgData.width)+x0)<<2;
+  var pixel0=[in_imgData.data[w0],in_imgData.data[w0+1],in_imgData.data[w0+2]];
+  var local_connectivity=[]
+  for (var dy=-1;dy<1;dy++) {
+    if (0>(dy+y0) || (dy+y0)>=in_imgData.height) continue;
+    for (var dx=-1;dx<(dy<0?2:1);dx++) {
+      if (0>(dx+x0) || (dx+x0)>=in_imgData.width) continue;
+      var w=((y0+dy)*in_imgData.width+x0+dx)<<2;
+      if (!this.component[w>>2]) continue;
+      var pixel=[in_imgData.data[w],in_imgData.data[w+1],in_imgData.data[w+2]]
+      if (this.metric(pixel0,pixel) <= this.threshold) {
+        var component=this.components[this.component[w>>2]];
+        if (local_connectivity.indexOf(component)==-1) {
+          local_connectivity.push(component);
+        }
+      }
+    }
+  }
+  local_connectivity.sort(function(a,b){return a<b?-1:((a==b)?0:1)});
+  return local_connectivity;
+}
+
+blobs.Pixel8ConnectivityBlobs.prototype.update_bbox=function(id,bbox,x,y,mean) {
+  if (bbox.x0>x) {bbox.dx+=bbox.x0-x; bbox.x0=x;}
+  if (bbox.y0>y) {bbox.dy+=bbox.y0-y; bbox.y0=y;}
+  if (bbox.x0+bbox.dx<x) bbox.dx=x-bbox.x0;
+  if (bbox.y0+bbox.dy<y) bbox.dy=y-bbox.y0;
+  bbox.count++;
+  for (i=0;i<3;i++)
+    bbox.mean[i]=((bbox.count-1)*bbox.mean[i]+mean[i])/bbox.count;
+  return bbox;
+}
+
+blobs.Pixel8ConnectivityBlobs.prototype.process=function(in_imgData,out_imgData) {
+  var blobs_arr=[]
+  this.component=[]; this.components=[];
+  this.nb_components=0;
+  var w=0;
+  for (var y=0; y < in_imgData.height; y++) {
+    for (var x=0; x < in_imgData.width; x++)
+    {
+      w=(y*in_imgData.width)+x;
+      var neighbours_connectivity=this.analyse_neighbours(in_imgData, x, y);
+      if (neighbours_connectivity.length==1) {
+        this.component[w]=(neighbours_connectivity[0]);
+      } else if (neighbours_connectivity.length>1) {
+        for (var i=1;i<neighbours_connectivity.length;i++) {
+          this.components[neighbours_connectivity[i]]=
+              this.components[neighbours_connectivity[0]];
+        }
+        this.component[w]=(this.components[neighbours_connectivity[0]]);
+      } else {
+          this.components[this.nb_components]=this.nb_components;
+          this.component[w]=(this.nb_components);
+          this.nb_components++;
+      }
+    }
+  }
+
+  var w=0, w4=0;
+  var components_bbox=[];
+  for (var y=0; y < in_imgData.height; y++)
+    for (var x=0; x < in_imgData.width; x++,w++,w4+=4)
+    {
+      this.component[w]=this.components[this.component[w]];
+      if (components_bbox[this.component[w]])
+        components_bbox[this.component[w]]=
+          this.update_bbox(
+            this.component[w],
+            components_bbox[this.component[w]],
+            x,y,
+            [in_imgData.data[w4],in_imgData.data[w4+1],in_imgData.data[w4+2]]);
+      else
+        components_bbox[this.component[w]]={
+                      x0:x,y0:y,dx:0,dy:0,
+                      count:1,mean:[in_imgData.data[w4],in_imgData.data[w4+1],
+                      in_imgData.data[w4+2]]};
+    }
+  if (out_imgData.data) {
+    for (idx in components_bbox) {
+    Tools.strokeBBox_on_imageData(out_imgData,components_bbox[idx],components_bbox[idx].mean);
+    }
+  }
+  return components_bbox;
+}
diff --git a/tools.js b/tools.js
index 40860ad..55320f5 100644
--- a/tools.js
+++ b/tools.js
@@ -59,6 +59,21 @@ Tools.copy_imageData_into_imageData=function(src_imageData,dest_imageData,x0,y0)
     }
 }
 
+Tools.copy_partial_imageData_into_imageData=
+function(src_imageData,x0,y0,dx,dy,dest_imageData,x1,y1) {
+
+  for (var src_y=y0; src_y<Math.min(y0+dy,src_imageData.height); src_y++)
+    for (var src_x=x0; src_x < Math.min(x0+dx,src_imageData.width); src_x++) {
+      var w_src=(src_y*src_imageData.width+src_x)<<2;
+      w_out=((y1+(src_y-y0))*dest_imageData.width+(x1+(src_x-x0)))<<2;
+      dest_imageData.data[w_out]=src_imageData.data[w_src];
+      dest_imageData.data[w_out+1]=src_imageData.data[w_src+1];
+      dest_imageData.data[w_out+2]=src_imageData.data[w_src+2];
+      dest_imageData.data[w_out+3]=src_imageData.data[w_src+3];
+
+    }
+}
+
 Tools.get_region_from_imageData=function(src_imageData,x0,y0,dx,dy) {
   var reg_imageData=src_imageData.ctxt.getImageData(
       src_imageData.orig_x+x0,src_imageData.orig_y+y0,
@@ -70,3 +85,35 @@ Tools.get_region_from_imageData=function(src_imageData,x0,y0,dx,dy) {
 
   return reg_imageData;
 }
+
+Tools.strokeBBox_on_imageData=function(imgData,bbox,col) {
+  Tools.strokeRect_on_imageData(imgData,bbox.x0,bbox.y0,bbox.dx,bbox.dy,col);
+}
+
+Tools.strokeRect_on_imageData=function(imgData,x0,y0,width,height,col) {
+  var w;
+
+  for (var x=x0;x<x0+width;x++) {
+    w=((y0)*imgData.width+x)<<2;
+    imgData.data[w]=col[0];imgData.data[w+1]=col[1];imgData.data[w+2]=col[2];
+    imgData.data[w+3]=255;
+    w=((y0+height)*imgData.width+x)<<2;
+    imgData.data[w]=col[0];imgData.data[w+1]=col[1];imgData.data[w+2]=col[2];
+    imgData.data[w+3]=255;
+  }
+  for (var y=y0;y<y0+height;y++) {
+    w=(y*imgData.width+x0)<<2;
+    imgData.data[w]=col[0];imgData.data[w+1]=col[1];imgData.data[w+2]=col[2];
+    imgData.data[w+3]=255;
+    w=(y*imgData.width+x0+width)<<2;
+    imgData.data[w]=col[0];imgData.data[w+1]=col[1];imgData.data[w+2]=col[2];
+    imgData.data[w+3]=255;
+  }
+}
+
+Tools.copySize=function(elt_id_1,elt_id_2) {
+  var elt1=document.getElementById(elt_id_1);
+  var elt2=document.getElementById(elt_id_2);
+  elt2.width=elt1.width;
+  elt2.height=elt1.height;
+}
diff --git a/tracking/appearance.js b/tracking/appearance.js
new file mode 100644
index 0000000..0a98aec
--- /dev/null
+++ b/tracking/appearance.js
@@ -0,0 +1,87 @@
+var appearance_tracking={}
+
+appearance_tracking.MeanShift=function(bbox,per_region_feature_func,metric_func,opt_options){
+  this.bbox=bbox;
+  this.window_width=opt_options&&opt_options.window_width?opt_options.window_width:3*bbox.dx;
+  this.window_height=opt_options&&opt_options.window_height?opt_options.window_height:3*bbox.dy;
+  this.step_x=opt_options&&opt_options.step_x?opt_options.step_x:bbox.dx/3;
+  this.step_y=opt_options&&opt_options.step_y?opt_options.step_y:bbox.dy/3;
+  this.per_region_feature_func=per_region_feature_func;
+  this.metric_func=metric_func;
+  this.threshold=opt_options&&opt_options.threshold?opt_options.threshold:Number.MAX_SAFE_INTEGER;
+  this.count=0;
+  this.update_model=opt_options&&opt_options.update_model?opt_options.update_model:false;
+}
+
+appearance_tracking.MeanShift.prototype.process=function(in_imgData,out_imgData) {
+  var in_width=in_imgData.width, in_height=in_imgData.height;
+
+  if (!this.bbox_feature) {
+    var _opt_options=this.bbox;
+    this.bbox_feature=this.per_region_feature_func(in_imgData,_opt_options);
+  }
+
+  this.pan_x=Math.round(this.window_width/2-this.bbox.dx/2);
+  this.pan_y=Math.round(this.window_height/2-this.bbox.dy/2);
+
+  var x_start=(this.bbox.x0-this.pan_x)>0?(this.bbox.x0-this.pan_x):0;
+  var y_start=(this.bbox.y0-this.pan_y)>0?(this.bbox.y0-this.pan_y):0;
+  var x_end=(this.bbox.x0+this.bbox.dx+this.pan_x) < in_width?
+            (this.bbox.x0+this.pan_x)
+            :(in_width-this.bbox.dx);
+  var y_end=(this.bbox.y0+this.bbox.dy+this.pan_y)<in_height?
+            this.bbox.y0+this.pan_y
+            :(in_height-this.bbox.dy);
+
+  var min=Number.MAX_SAFE_INTEGER;
+  var min_bbox,min_bbox_feature;
+  var count_y=in_imgData.height;
+
+  if (out_imgData) {
+    Tools.copy_partial_imageData_into_imageData(in_imgData,
+                0,0,in_imgData.width,in_imgData.height,
+                out_imgData,0,0);
+    Tools.strokeBBox_on_imageData(out_imgData,{x0:x_start,y0:y_start,dx:x_end-x_start,dy:y_end-y_start},[64,64,64,255]);
+  }
+
+  for (var y=y_start;y<y_end;y+=this.step_y) {
+    var count_x=0;
+    for (var x=x_start;x<x_end;x+=this.step_x) {
+      var local_bbox={
+                    x0:(x+this.bbox.dx<in_width)?x:in_width-this.bbox.dx,
+                    y0:(y+this.bbox.dy<in_height)?y:in_height-this.bbox.dy,
+                    dx:this.bbox.dx,
+                    dy:this.bbox.dy
+                  }
+      if (out_imgData) {
+        Tools.copy_partial_imageData_into_imageData(in_imgData,
+                    local_bbox.x0,local_bbox.y0,local_bbox.dx,local_bbox.dy,
+                    out_imgData,count_x,count_y);
+        Tools.strokeBBox_on_imageData(out_imgData,local_bbox,[0,64,64,255]);
+                  }
+
+      var local_bbox_feature=this.per_region_feature_func(in_imgData,local_bbox);
+      if (!local_bbox_feature) continue;
+      var fun=function(a,b) {return a-b;}
+      var diff=this.metric_func(this.bbox_feature,local_bbox_feature);
+      if (diff < min) {
+        min=diff;
+        min_bbox=local_bbox;
+        min_bbox_feature=local_bbox_feature;
+      }
+      count_x+=this.bbox.dx;
+    }
+    count_y+=this.bbox.dy;
+  }
+
+  if (min < this.threshold && this.bbox!=min_bbox) {
+      this.count++;
+      this.bbox=min_bbox;
+      if (this.update_model)
+        this.bbox_feature=min_bbox_feature;
+  }
+  if (out_imgData) {
+    Tools.strokeBBox_on_imageData(out_imgData,this.bbox,[255,0,255,255]);
+  }
+  return this.bbox;
+}
-- 
GitLab