microb 2010
[aversive.git] / projects / microb2010 / sensorboard / img_processing.c
diff --git a/projects/microb2010/sensorboard/img_processing.c b/projects/microb2010/sensorboard/img_processing.c
new file mode 100644 (file)
index 0000000..d030478
--- /dev/null
@@ -0,0 +1,1619 @@
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <aversive/pgmspace.h>
+#include <aversive/error.h>
+
+#include <stdint.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <vect_base.h>
+#include <lines.h>
+#include <polygon.h>
+
+#include "img_processing.h"
+
+
+#define debug_printf(fmt, ...) printf_P(PSTR(fmt), ##__VA_ARGS__)
+
+#ifndef HOST_VERSION
+
+#include <aversive.h>
+#include <pwm_ng.h>
+#include <pid.h>
+#include <time.h>
+#include <quadramp.h>
+#include <blocking_detection_manager.h>
+#include <rdline.h>
+
+#include <control_system_manager.h>
+#include <adc.h>
+#include <spi.h>
+#include <ax12.h>
+
+#include "main.h"
+
+#define IMG_DEBUG(args...) DEBUG(E_USER_IMGPROCESS, args)
+#define IMG_NOTICE(args...) NOTICE(E_USER_IMGPROCESS, args)
+#define IMG_ERROR(args...) ERROR(E_USER_IMGPROCESS, args)
+
+#else
+
+#define IMG_DEBUG(args...) debug_printf(args)
+#define IMG_NOTICE(args...) debug_printf(args)
+#define IMG_ERROR(args...) debug_printf(args)
+
+#endif
+
+
+
+
+#define OBJECT_MINIMUM_DEMI_PERIMETER (2*3)
+/* store object in pool if never seen 
+ * returns:
+ * 1 if new object
+ * 0 if already known
+ */
+int store_obj(Object_bb* tab_o,  int total, Object_bb*o)
+{
+       uint8_t i;
+
+       if (o->x_max - o->x_min + o->y_max - o->y_min < OBJECT_MINIMUM_DEMI_PERIMETER) 
+               return 0;
+
+       for (i=0;i<total;i++){
+               if (!memcmp(&tab_o[i], o, sizeof(Object_bb)))
+                       return 0;
+                       
+               if (tab_o[i].x_min==0 && tab_o[i].x_max==0 && 
+                   tab_o[i].y_min==0 && tab_o[i].y_max==0){
+                       memcpy(&tab_o[i], o, sizeof(Object_bb));
+                       return 1;
+               }
+                       
+       }
+       return 0;
+}
+
+/* step around object of given color and compute its bounding box */
+void object_find_bounding_box(unsigned char* data, 
+                             int16_t x_in, int16_t y_in, 
+                             int16_t start_x, int16_t start_y, 
+                             int16_t color, int16_t color_w,Object_bb* o)
+{
+       int16_t pos_x = start_x;
+       int16_t pos_y = start_y;
+       int16_t start =0;
+       int16_t len = 0;
+       int16_t count_stand = 0;
+
+       vect_t v, vi;
+
+
+       v.x = 1;
+       v.y = 0;
+    
+       vi = v;
+    
+       o->x_max = 0;
+       o->y_max = 0;
+       o->x_min = x_in;
+       o->y_min = y_in;
+    
+       
+       while(1){
+               if (pos_x == start_x && pos_y == start_y){
+                       count_stand++;
+                       if (count_stand>4)
+                               break;
+                       if( v.x == vi.x && v.y == vi.y && start)
+                               break;
+               }
+                
+               if (pos_x<o->x_min)
+                       o->x_min = pos_x;
+               if (pos_y<o->y_min)
+                       o->y_min = pos_y;
+               if (pos_x>o->x_max)
+                       o->x_max = pos_x;
+               if (pos_y>o->y_max)
+                       o->y_max = pos_y;
+        
+               /* is next pixel is good color */
+               if (data[(pos_y+v.y)*x_in + pos_x+v.x] != color){
+                       pos_x = pos_x+v.x;
+                       pos_y = pos_y+v.y;
+                       len++;
+                       vect_rot_retro(&v);
+                       start = 1;
+                       continue;
+               }    
+               vect_rot_trigo(&v);
+       }    
+       
+       o->len = len;
+}
+
+
+
+
+
+/* step around object of given color and computes its polygon */
+void object_find_poly(unsigned char* data, 
+                     int16_t x_in, int16_t y_in, 
+                     int16_t start_x, int16_t start_y, 
+                     int16_t color, int16_t color_w, Object_poly* o)
+{
+       int16_t pos_x = start_x;
+       int16_t pos_y = start_y;
+       int16_t start =0;
+       int16_t len = 0;
+       int16_t count_stand = 0;
+       uint16_t pt_step, pt_num;
+       vect_t v, vi;
+
+       v.x = 1;
+       v.y = 0;
+
+       vi = v;    
+       
+       pt_step = o->len/POLY_MAX_PTS + 1;
+       
+       pt_num = 0;
+
+       while(1){
+               if (pos_x == start_x && pos_y == start_y){
+                       count_stand++;
+                       if (count_stand>4)
+                               break;
+                       if( v.x == vi.x && v.y == vi.y && start)
+                               break;
+               }
+        
+               /* is next pixel is good color */
+               if (data[(pos_y+v.y)*x_in + pos_x+v.x] != color){
+                       pos_x = pos_x+v.x;
+                       pos_y = pos_y+v.y;
+                       len++;
+                       
+                       if (len >pt_num*pt_step){
+                               o->pts[pt_num].x = pos_x;
+                               o->pts[pt_num].y = pos_y;
+                               pt_num+=1;
+                               if (pt_num>=POLY_MAX_PTS)
+                                       break;
+                       }
+
+                       vect_rot_retro(&v);
+                       start = 1;
+                       continue;
+               }    
+               vect_rot_trigo(&v);
+       }    
+
+       o->pts_num = pt_num;
+
+}
+
+#define PT_LEFT 0
+#define PT_RIGHT 2
+#define PT_TOP 1
+#define PT_DOWN 3
+
+/* return most left/right/top/down pts indexes of given polygon */
+void object_find_extrem_points_index(Object_poly* o, 
+                                    unsigned int *pts)
+{
+       unsigned int i;
+       for (i=0;i<4;i++)
+               pts[i] = 0;
+
+
+       for (i=1;i<o->pts_num;i++){
+               if (o->pts[i].x < o->pts[pts[PT_LEFT]].x)
+                       pts[PT_LEFT] = i;
+               if (o->pts[i].x > o->pts[pts[PT_RIGHT]].x)
+                       pts[PT_RIGHT] = i;
+               if (o->pts[i].y < o->pts[pts[PT_TOP]].y)
+                       pts[PT_TOP] = i;
+               if (o->pts[i].y > o->pts[pts[PT_DOWN]].y)
+                       pts[PT_DOWN] = i;
+       }
+
+}
+
+
+#define NUM_CALIPERS 4
+/* for debug purpose: display a vector on image */
+void draw_pt_vect(unsigned char *buf, int16_t x_in, int16_t y_in, 
+                 vect_t *v, point_t p)
+{
+       unsigned int i;
+       float n;
+       int16_t x, y;
+       float coef=1.0;
+
+       if (!v->x && !v->y)
+               return;
+
+       n = vect_norm(v);
+       coef = 1/n;
+       for (i=0;i<5;i++){
+               x = p.x;
+               y = p.y;
+               
+               x+=(float)((v->x)*(float)i*(float)coef);
+               y+=(float)((v->y)*(float)i*(float)coef);
+               if ((x== p.x) && (y == p.y))
+                       buf[y*x_in+x] = 0x0;
+               else
+                       buf[y*x_in+x] = 0x0;
+       }
+       
+}
+
+#define CAL_X 3
+#define CAL_Y 0
+
+
+/* compute minimum rectangle area including the given convex polygon */
+void object_poly_get_min_ar(Object_poly *o, unsigned int *pts_index_out, 
+                            vect_t *v_out, vect_t *r1, vect_t*r2)
+{
+       vect_t calipers[NUM_CALIPERS];
+       vect_t edges[NUM_CALIPERS];
+
+       unsigned int i;
+       unsigned int calipers_pts_index[NUM_CALIPERS];
+       float angles[NUM_CALIPERS];
+       float min_angle;
+       float total_rot_angle = 0;
+       int caliper_result_index;
+
+       vect_t res1, res2;
+       float ps, n1, n2, caliper_n;
+
+       float aera_tmp;
+       /* XXX hack sould be max*/
+       float aera_min=0x100000;
+
+       object_find_extrem_points_index(o, calipers_pts_index);
+               
+       calipers[0].x = 0;
+       calipers[0].y = 1;
+
+       calipers[1].x = -1;
+       calipers[1].y = 0;
+
+       calipers[2].x = 0;
+       calipers[2].y = -1;
+
+       calipers[3].x = 1;
+       calipers[3].y = 0;
+
+
+       while (total_rot_angle <= M_PI/2){
+
+               for (i=0;i<NUM_CALIPERS;i++){
+                       /* compute polygon edge vector */
+                       edges[i].x = o->pts[(calipers_pts_index[i] + 1)%o->pts_num].x - 
+                               o->pts[calipers_pts_index[i]].x;
+                       edges[i].y = o->pts[(calipers_pts_index[i] + 1)%o->pts_num].y - 
+                               o->pts[calipers_pts_index[i]].y;
+
+                       /* compute angle between caliper and polygon edge */
+                       angles[i] = vect_get_angle(&edges[i], &calipers[i]);
+               }
+
+               /* find min angle */
+               min_angle = angles[0];
+               caliper_result_index = 0;
+               for (i=1;i<NUM_CALIPERS;i++){
+                       if (angles[i]<min_angle){
+                               min_angle = angles[i];
+                               caliper_result_index = i;
+                       }
+               }
+
+               /* rotate calipers */
+               calipers[caliper_result_index] = edges[caliper_result_index];
+               
+               for (i=caliper_result_index; i<caliper_result_index + o->pts_num; i++){
+                       calipers[(i+1) % NUM_CALIPERS] = calipers[i % NUM_CALIPERS];
+                       vect_rot_trigo(&calipers[(i+1) % NUM_CALIPERS]);
+               }
+
+               /* update calipers point */
+               for (i=0;i<NUM_CALIPERS;i++){
+                       if (angles[i]==min_angle)
+                               calipers_pts_index[i] = (calipers_pts_index[i] + 1) % o->pts_num;
+               }
+               
+               res1.x = o->pts[calipers_pts_index[2]].x - o->pts[calipers_pts_index[0]].x;
+               res1.y = o->pts[calipers_pts_index[2]].y - o->pts[calipers_pts_index[0]].y;
+
+               res2.x = o->pts[calipers_pts_index[3]].x - o->pts[calipers_pts_index[1]].x;
+               res2.y = o->pts[calipers_pts_index[3]].y - o->pts[calipers_pts_index[1]].y;
+               
+               ps = vect_pscal(&res1, &calipers[CAL_X]);
+               n1 = vect_norm(&res1);
+               caliper_n = vect_norm(&calipers[CAL_X]);
+               caliper_n*=caliper_n;
+
+               res1 = calipers[CAL_X];
+               
+               res1.x *= ps/(caliper_n);
+               res1.y *= ps/(caliper_n);
+
+
+               ps = vect_pscal(&res2, &calipers[CAL_Y]);
+               n1 = vect_norm(&res2);
+
+               res2 = calipers[CAL_Y];
+               
+               res2.x *= ps/(caliper_n);
+               res2.y *= ps/(caliper_n);
+               
+               n1 = vect_norm(&res1);
+               n2 = vect_norm(&res2);
+               
+               aera_tmp = n1*n2;
+               
+               if (aera_min >aera_tmp){
+                       aera_min = aera_tmp;
+                       for (i=0;i<NUM_CALIPERS;i++){
+                               pts_index_out[i] = calipers_pts_index[i];
+                       }
+                       *v_out = calipers[0];
+                       *r1 = res1;
+                       *r2 = res2;
+                       
+               }
+               total_rot_angle+=min_angle;
+       }
+
+       return;
+}
+
+#define COEF_CALIP 1
+/* transform caliper to rectangle coordinates */
+int object_poly_caliper_to_rectangle(Object_poly *o, 
+                                    unsigned int *pts_index_out, vect_t* caliper, 
+                                    vect_t *r1, vect_t*r2, point_t *p)
+{
+       line_t l1, l2;
+       vect_t caliper_tmp;
+       int ret, i;
+       double mp_x, mp_y;
+       point_t p_int1;//, p_int2;
+
+       point_t p1, p2;
+
+       caliper_tmp = *caliper;
+
+       //IMG_DEBUG("cal: %" PRIi32 " %" PRIi32 "", caliper_tmp.x, caliper_tmp.y);
+  
+       mp_x = 0;
+       mp_y = 0;
+
+       /* to be precise, calc 4 intersection of 4 calipers */
+       for (i=0;i<NUM_CALIPERS;i++){
+               p1.x = o->pts[pts_index_out[i]].x;
+               p1.y = o->pts[pts_index_out[i]].y;
+  
+               p2.x = p1.x+COEF_CALIP*caliper_tmp.x;
+               p2.y = p1.y+COEF_CALIP*caliper_tmp.y;
+  
+               pts2line(&p1, &p2, &l1);
+       
+               vect_rot_trigo(&caliper_tmp);  
+  
+               p1.x = o->pts[pts_index_out[(i+1)%NUM_CALIPERS]].x;
+               p1.y = o->pts[pts_index_out[(i+1)%NUM_CALIPERS]].y;
+  
+               p2.x = p1.x+COEF_CALIP*caliper_tmp.x;
+               p2.y = p1.y+COEF_CALIP*caliper_tmp.y;
+  
+               pts2line(&p1, &p2, &l2);
+       
+               ret = intersect_line(&l1, &l2, &p_int1);
+               if (ret!=1)
+                       return 0;
+               //IMG_DEBUG("int1 (%d): %" PRIi32 " %" PRIi32 " ", ret, p_int1.x, p_int1.y);
+  
+               mp_x+=p_int1.x;
+               mp_y+=p_int1.y;
+        
+       }
+  
+  
+  
+       p->x = lround(mp_x/NUM_CALIPERS);
+       p->y = lround(mp_y/NUM_CALIPERS);
+  
+
+       return 1;
+
+}
+
+#define OBJECT_DIM 5
+/* split zone in many column's sized area */
+int split_rectangle(point_t *p, vect_t *r1, vect_t* r2, uint8_t max_zone, zone* zones, uint8_t color)
+{
+       int n1, n2;
+       int i, j;
+       int index=0;
+       int r1_s, r2_s;
+       point_t ptmp;
+  
+  
+       n1 = vect_norm(r1);
+       n2 = vect_norm(r2);
+
+       r1_s = n1/OBJECT_DIM;
+       r2_s = n2/OBJECT_DIM;
+
+       if (!r1_s || ! r2_s)
+               return 0;
+  
+       ptmp.x = p->x - r1->x/2 - r2->x/2 + (r1->x/(r1_s*2))+(r2->x/(r2_s*2));
+       ptmp.y = p->y - r1->y/2 - r2->y/2 + (r1->y/(r1_s*2))+(r2->y/(r2_s*2));
+  
+       for(i=0;i<r1_s;i++){
+               for(j=0;j<r2_s;j++){
+                       zones[index].p.x = ptmp.x + (i*r1->x)/r1_s+(j*r2->x)/r2_s;
+                       zones[index].p.y = ptmp.y + (i*r1->y)/r1_s+(j*r2->y)/r2_s;
+                       zones[index].h = color;
+                       zones[index].valid = 1;
+
+                       index++;
+                       if (index>=max_zone)
+                               return index;
+
+
+               }
+       }
+    
+       return index;
+}
+
+#define OBJECT_SEMI_DIM (OBJECT_DIM/2)
+#define MIN_SURFACE_PERCENT 50
+#define HIGHER_MAX_PIXEL 5
+
+
+int zone_has_enought_pixels(unsigned char* data, int16_t x_in, int16_t y_in, zone* z)
+{
+       int x, y;
+       uint16_t count, total_pix, higher_pixels;
+
+       count = 0;
+       total_pix=0;
+       higher_pixels = 0;
+
+       for (x = -OBJECT_SEMI_DIM; 
+            (x <= OBJECT_SEMI_DIM) && (higher_pixels < HIGHER_MAX_PIXEL); 
+            x++){
+               for (y = -OBJECT_SEMI_DIM; 
+                    (y <= OBJECT_SEMI_DIM) && (higher_pixels < HIGHER_MAX_PIXEL); 
+                    y++){
+                       total_pix++;
+                       if (data[x_in * (y + z->p.y) + x + z->p.x] == z->h)
+                               count++;
+                       
+                       if (data[x_in * (y + z->p.y) + x + z->p.x] > z->h)
+                               higher_pixels++;
+                       
+               }
+               
+       }
+
+       IMG_DEBUG("has enougth pixel (h: %d x %"PRIi32": y:%"PRIi32") total: %d/%d (tt: %d, hmax: %d)", z->h, z->p.x, z->p.y, 
+                 count, (total_pix *  MIN_SURFACE_PERCENT) / 100, total_pix, higher_pixels);
+                       
+       if ((count > (total_pix *  MIN_SURFACE_PERCENT) / 100) && 
+           (higher_pixels <HIGHER_MAX_PIXEL))
+               return 1;
+       
+       return 0;
+}
+
+int zone_filter_min_surface(unsigned char* data, int16_t x_in, int16_t y_in, 
+                           uint8_t color, unsigned int zones_num, zone* p)
+{
+       int i;
+       
+       for (i = 0; i < zones_num ; i++){
+               if (zone_has_enought_pixels(data, x_in, y_in, &p[i]))
+                       continue;
+
+               p[i].valid = 0;         
+       }
+
+       IMG_NOTICE("num zone after min surf: %d", zones_num);
+       
+       return zones_num;
+       
+}
+
+/* center is 15 cm radius*/
+#define CENTER_RADIUS 15
+
+/* the complete column must be in the drop zone*/
+#define CENTER_MAX_DIST (15-3)
+
+/* the column must not be too close from center*/
+#define CENTER_MIN_DIST (8)
+
+int zone_filter_center(unsigned int zones_num, zone* p, int16_t center_x, int16_t center_y, int tweak_min_margin)
+{
+       int i;
+       vect_t v;
+       
+       for (i = 0; i < zones_num; i++){
+               
+               v.x = p[i].p.x - center_x;
+               v.y = p[i].p.y - center_y;
+               IMG_DEBUG("square dist to center %"PRIi32" (%d %d)",
+                         v.x*v.x + v.y*v.y,  (CENTER_MIN_DIST+tweak_min_margin) * (CENTER_MIN_DIST+tweak_min_margin), CENTER_MAX_DIST * CENTER_MAX_DIST);
+
+               if (v.x*v.x + v.y*v.y < CENTER_MAX_DIST * CENTER_MAX_DIST && 
+                   v.x*v.x + v.y*v.y > (CENTER_MIN_DIST+tweak_min_margin) * (CENTER_MIN_DIST+tweak_min_margin))
+                       continue;
+
+               p[i].valid = 0;
+
+       }
+
+       return zones_num;
+}
+
+#define MAX_DIST_TO_ZONE 2
+
+unsigned int zone_filter_zone_rect(unsigned int zones_num, zone* p, int16_t center_x, int16_t center_y , uint8_t working_zone)
+{
+       int i;
+       
+       for (i = 0; i < zones_num; i++){                
+               
+               IMG_DEBUG("rct x:%"PRIi32" y:%"PRIi32" (centerx: %d)",p[i].p.x , p[i].p.y,  center_x);
+               
+               if ((p[i].p.x > center_x - MAX_DIST_TO_ZONE) && (p[i].h > working_zone))
+                       continue;
+
+               p[i].valid = 0;
+       }
+
+       return zones_num;
+}
+
+
+/* delete point to render polygon convex */
+int object_poly_to_convex(Object_poly *o)
+{
+       unsigned int i, j;
+       vect_t v, w;
+       int16_t z;
+       unsigned int del_pts_num = 0;
+       
+       for (i=0;i<o->pts_num;){
+               v.x = o->pts[(i + o->pts_num - 1)%o->pts_num].x - o->pts[i].x;
+               v.y = o->pts[(i + o->pts_num - 1)%o->pts_num].y - o->pts[i].y;
+
+               w.x = o->pts[(i+1)%o->pts_num].x - o->pts[i].x;
+               w.y = o->pts[(i+1)%o->pts_num].y - o->pts[i].y;
+               
+               z = vect_pvect(&v, &w);
+               if (z>0){
+                       i+=1;
+                       continue;
+               }
+               
+               /* found a convex angle (or colinear points) */
+               for (j = i; j < o->pts_num-1; j++){
+                       o->pts[j] = o->pts[j+1];
+               }
+               if (i!=0)
+                       i-=1;
+               o->pts_num--;
+               del_pts_num++;
+       }
+
+       return del_pts_num;
+}
+
+
+
+
+
+#define DEFAULT_COLOR 255
+/* scan all image and find objects*/
+unsigned char *parcour_img(unsigned char* data, int16_t x_in, int16_t y_in, 
+                          Object_bb *sac_obj, Object_poly *sac_obj_poly, int16_t max_obj)
+{
+       int16_t i, obj_num;
+
+       uint8_t in_color=0;    
+       int16_t etat;
+
+       Object_bb o;
+       int ret;
+
+       obj_num = 0;
+       /*
+         first, delete borders
+       */
+       for (i=0;i<x_in;i++){
+               data[i] = 0;
+               data[(y_in - 1) * x_in + i] = 0;
+       }
+
+       for (i=0;i<y_in;i++){
+               data[i * x_in] = 0;
+               data[i * x_in + x_in - 1] = 0;
+       }
+      
+
+    
+       etat = 0; 
+       /*
+         0 look for color (object or edge)
+         1 look for edge end
+       */
+    
+       for (i=1;i<x_in*y_in;i++){
+               switch(etat){
+               case 0:
+                       //we are in the dark
+                       switch(data[i]){
+                       case 0:
+                               //look for in dark
+                               break;
+                               /*
+                                 case 1:
+                                 case 2:
+                                 case 0x15:
+                                 case 0x24:
+                               */
+                       default:
+                               in_color = data[i];
+                               etat = 1;
+                               // we found an object
+                               object_find_bounding_box(data, x_in, y_in, (i-1)%x_in, (i-1)/x_in, data[i], 255, &o);
+
+                               ret = store_obj(sac_obj,  max_obj, &o);
+                               /* if new object, process rotating calipers */
+                               if (ret){
+                                       sac_obj_poly[obj_num].len = o.len;
+                                       object_find_poly(data, x_in, y_in, 
+                                                        (i-1)%x_in, (i-1)/x_in, 
+                                                        data[i], 255, &sac_obj_poly[obj_num]);
+                                       IMG_DEBUG("%d",sac_obj_poly[obj_num].pts_num);
+                                       object_poly_to_convex(&sac_obj_poly[obj_num]);
+                                       /*
+                                         for (j=0;j<sac_obj_poly[obj_num].pts_num;j++){
+                                         data[sac_obj_poly[obj_num].pts[j].y*x_in + sac_obj_poly[obj_num].pts[j].x] = 8;
+                                         }*/
+                                       sac_obj_poly[obj_num].color = data[i];
+                                       obj_num++;
+                               }
+                        
+                               break;
+
+                       case DEFAULT_COLOR:
+                               //we must out of color
+                               break;
+                       }    
+                       break;
+
+               case 1:
+                       if (data[i] != in_color){
+                               i--;
+                               etat = 0;
+                       }
+                       /*
+                         we are in a color and want to go out of it
+                       */
+                       break;
+
+               }    
+        
+        
+        
+       }    
+
+    
+       return data;
+    
+    
+}    
+/* space between twin tower is 13 pixels*/
+#define SPACE_INTER_TWIN_TOWER (13)
+
+#define SPACE_INTER_TWIN_TOWER_TOLERENCE 3
+
+
+
+/* find best twin tower for each zone */
+void find_twin_tower(uint8_t zones_num, zone* zones, int8_t sisters[MAX_ZONES][MAX_SISTER_PER_ZONE], 
+                    int16_t center_x, int16_t center_y)
+{
+
+       uint8_t i, j;
+       uint8_t z_n;
+       int n1, n2;
+       unsigned int z1, z2, z3;
+       //int32_t scal1, scal2;
+       vect_t v, v1, v2;
+       line_t l;
+       point_t p;
+       unsigned int good_zone;
+       double dist, dist2;
+       unsigned int current_sister;
+
+       /* init dummy sisters */
+       for (i = 0; i < zones_num; i++)
+               for (j = 0; j < MAX_SISTER_PER_ZONE; j++)
+                       sisters[i][j] = -1;
+
+
+
+       for (z_n = 0; z_n < zones_num; z_n++){
+               if (!zones[z_n].valid)
+                       continue;
+
+               current_sister = 0;
+
+               for (i = 0; i < zones_num; i++){
+
+
+                       /* we already have max sisters */
+                       if (current_sister >= MAX_SISTER_PER_ZONE)
+                               break;
+
+
+                       if (!zones[i].valid)
+                               continue;
+
+
+                       if (i == z_n)
+                               continue;
+            
+                       /* twin tower must have same high */
+                       if (zones[i].h != zones[z_n].h)
+                               continue;
+
+                       IMG_DEBUG("test sisters (%"PRIi32" %"PRIi32") (%"PRIi32" %"PRIi32")", 
+                                 zones[z_n].p.x,   zones[z_n].p.y,
+                                 zones[i].p.x,   zones[i].p.y);
+                       
+                       
+                       dist = sqrt( (zones[i].p.x - zones[z_n].p.x) * (zones[i].p.x - zones[z_n].p.x) + 
+                                    (zones[i].p.y - zones[z_n].p.y) * (zones[i].p.y - zones[z_n].p.y) );
+                       
+                       
+                       IMG_DEBUG("sister space is %2.2f may be near %d", dist, SPACE_INTER_TWIN_TOWER);
+                       
+                       /* 
+                          twin tower must be close/far enought to drop lintel
+                        */
+                       if (ABS(dist - SPACE_INTER_TWIN_TOWER) > SPACE_INTER_TWIN_TOWER_TOLERENCE)
+                               continue;
+
+
+                       pts2line(&zones[i].p, &zones[z_n].p, &l);
+                       
+                       /* 
+                          test the paralelism of the temple:
+                          zone may be on same distance from center 
+                       */
+                       
+
+                       v1.x = zones[z_n].p.x - center_x;
+                       v1.y = zones[z_n].p.y - center_y;
+                       
+                       dist = vect_norm(&v1);
+
+                       v2.x = zones[i].p.x - center_x;
+                       v2.y = zones[i].p.y - center_y;
+                       
+                       dist2 = vect_norm(&v2);
+
+                       IMG_DEBUG("zone dist %2.2f %2.2f", dist, dist2);
+                       if (ABS(dist-dist2) > 3){
+                               IMG_DEBUG("bad parallelism %2.2f", ABS(dist-dist2));
+                               continue;
+                       }
+                               
+                       
+
+            
+                       /* no other aligned tower to avoid dropping on a lintel 
+                        *  (3 aligned zone may mean lintel) 
+                        */
+
+                       good_zone = 1;
+            
+                       for (j = 0; j < zones_num; j++){
+                               if (j==i ||j == z_n)
+                                       continue;
+            
+                               /* if third zone, but lower */
+                               if (zones[j].h <= zones[i].h)
+                                       continue;
+            
+                               /* 
+                                  check distance from dropping zone to
+                                  line formed by twin towers
+                               */
+            
+                               proj_pt_line(&zones[j].p, &l, &p);
+
+
+                               /* test if projected point is in the segement */
+                               
+
+                               v.x = zones[z_n].p.x - zones[i].p.x;
+                               v.y = zones[z_n].p.y - zones[i].p.y;
+
+                               v1.x = p.x - zones[i].p.x;
+                               v1.y = p.y - zones[i].p.y;
+
+                               n1 = vect_pscal_sign(&v, &v1);
+
+                               v.x = -v.x;
+                               v.y = -v.y;
+
+                               v1.x = p.x - zones[z_n].p.x;
+                               v1.y = p.y - zones[z_n].p.y;
+                               
+
+                               n2 =vect_pscal_sign(&v, &v1);
+
+                               v.x = p.x - zones[j].p.x;
+                               v.y = p.y - zones[j].p.y;
+            
+                               dist = vect_norm(&v);
+                               IMG_DEBUG("dist pt  h %d n: (%d %d) (%"PRIi32" %"PRIi32") to line %2.2f", zones[j].h, n1, n2, zones[j].p.x, zones[j].p.y, dist);
+
+            
+                               if ((n1>=0 && n2>=0) && dist < OBJECT_DIM+2.){
+                                       good_zone = 0;
+                                       break;
+                               }
+                                       
+            
+                               /* test if zone is far from points*/
+                               
+                               v1.x = zones[j].p.x - zones[z_n].p.x;
+                               v1.y = zones[j].p.y - zones[z_n].p.y;
+            
+                               dist = vect_norm(&v1);
+                               IMG_DEBUG("dist pt to z1 %2.2f", dist);
+            
+                               if (dist < OBJECT_DIM){
+                                       good_zone = 0;
+                                       break;
+                               }
+                               
+                               v2.x = zones[j].p.x - zones[i].p.x;
+                               v2.y = zones[j].p.y - zones[i].p.y;
+            
+            
+                               dist = vect_norm(&v2);
+                               IMG_DEBUG("dist pt to z2 %2.2f", dist);
+            
+                               if (dist < OBJECT_DIM){
+                                       good_zone = 0;
+                                       break;
+                               }
+
+                               
+            
+                               z1 = i;
+                               z2 = z_n;
+                               z3 = j;
+            
+
+                               
+            
+            
+                               /*
+                                 XXX may be a lintel on lintel !!
+                                */
+
+                       }
+            
+                       if (!good_zone)
+                               continue;
+            
+                       IMG_DEBUG("sisters ok (%"PRIi32" %"PRIi32") (%"PRIi32" %"PRIi32")", 
+                                 zones[z_n].p.x,   zones[z_n].p.y,
+                                 zones[i].p.x,   zones[i].p.y);
+
+                       
+                       sisters[z_n][current_sister] = i;
+                       current_sister++;
+               }
+       }
+}
+
+/* test if a higher zone is too close */
+int test_close_zone(uint8_t zones_num, zone* zones, unsigned int z_n)
+{
+       uint8_t i;
+       vect_t v;
+       double dist;
+
+       for (i = 0; i < zones_num; i++){
+               if (i == z_n)
+                       continue;
+               if (zones[i].h <= zones[z_n].h)
+                       continue;
+               
+               v.x = zones[i].p.x - zones[z_n].p.x;
+               v.y = zones[i].p.y - zones[z_n].p.y;
+
+               dist = vect_norm(&v);
+               //IMG_DEBUG("dist pt to pt %2.2f", dist);
+                       
+               if (dist < OBJECT_DIM){
+                       return 1;
+               }
+
+       }
+
+       return 0;
+}
+
+#define MAX_COLUMN 4
+#define MAX_LINTEL 2
+
+drop_column_zone drop_c[MAX_COLUMN];
+drop_lintel_zone drop_l[MAX_LINTEL];
+
+
+void reset_drop_zone(void)
+{
+       memset(drop_c, 0, sizeof(drop_c));
+       memset(drop_l, 0, sizeof(drop_l));
+
+}
+
+
+void display_drop_zones(uint8_t n_columns, uint8_t n_lintels, zone* zones)
+{
+       unsigned int i;
+
+       for (i=0;i<n_columns;i++)
+               IMG_NOTICE("c %d:(h:%d) (%"PRIi32" %"PRIi32") valid=%d",
+                            i, drop_c[i].h,
+                            zones[drop_c[i].z].p.x, zones[drop_c[i].z].p.y,
+                            drop_c[i].valid);
+
+       for (i=0;i<n_lintels;i++)
+               IMG_NOTICE("l %d:(h:%d) (%"PRIi32" %"PRIi32") "
+                            "(%"PRIi32" %"PRIi32") valid=%d",
+                            i, drop_l[i].h,
+                            zones[drop_l[i].z1].p.x, zones[drop_l[i].z1].p.y, 
+                            zones[drop_l[i].z2].p.x, zones[drop_l[i].z2].p.y, drop_l[i].valid);
+       
+}
+
+#define MY_MAX(a, b) ((a)>(b)?(a):(b))
+
+
+#if 0
+#define MAX_DROP_HIGH  8
+
+
+/* 
+   recursive function to maximize points during object
+   dropping, given lintel/column number
+   working zone may be 1, 2 or 3
+ */
+unsigned int solve_objects_dropping(unsigned int points, unsigned int points_max,
+                                   uint8_t n_columns, uint8_t n_lintels, 
+                                   uint8_t zones_num, zone* zones, int8_t sisters[MAX_ZONES][MAX_SISTER_PER_ZONE], uint8_t working_zone)
+{
+       
+       uint8_t i, j;
+       unsigned int points_calc;
+       //unsigned int points_added = 0;
+       int sister;
+       int ret;
+
+
+       /* if no more objects, return points */
+       if (n_columns == 0 && n_lintels == 0)
+               return MY_MAX(points, points_max);
+       
+       /* start by putting columns if so */
+       for (i = 0; i < zones_num; i++){
+               if (zones[i].h >= MAX_DROP_HIGH)
+                       continue;
+
+               if (n_columns){
+
+                       ret = test_close_zone(zones_num, zones, i);
+                       if (ret)
+                               continue;
+
+                       zones[i].h++;
+                       points_calc = solve_objects_dropping(points + zones[i].h, points_max,
+                                                            n_columns - 1, n_lintels, 
+                                                            zones_num, zones, sisters, working_zone);
+                       
+                       if (points_calc > points_max){
+                               points_max = points_calc;
+                               drop_c[n_columns - 1].z = i;
+                               drop_c[n_columns - 1].h = zones[i].h;
+                               drop_c[n_columns - 1].valid  = 1;
+                               
+                       }
+                       zones[i].h--;
+               }
+               /* we must depose all columns before dropping lintels */
+               else if (n_lintels){
+                       
+                       /* dont drop lintel on ground */
+                       if (zones[i].h  <= working_zone)
+                               continue;
+
+                       /* XXX todo need get second zone near selected one */
+                       //ret = find_twin_tower(zones_num, zones, i, &sister);
+
+                       for (j = 0; j < MAX_SISTER_PER_ZONE; j++){
+                               sister = sisters[i][j];
+                               if (sister == -1)
+                                       break;
+                               if (zones[i].h != zones[sister].h){
+                                       sister = -1;
+                               }
+                               
+                       }
+
+                       if (sister == -1)
+                               continue;
+                       //IMG_DEBUG("sister found: %d %d (h=%d %p)", i, sister, zones[i].h, &zones[i].h);
+                       
+                       zones[i].h++;
+                       zones[sister].h++;
+
+                       
+                       points_calc = solve_objects_dropping(points + zones[i].h * 3, points_max,
+                                                            n_columns, n_lintels - 1, 
+                                                            zones_num, zones, sisters, working_zone);
+                       
+                       if (points_calc > points_max){
+                               points_max = points_calc;
+
+                               drop_l[n_lintels - 1].z1 = i;
+                               drop_l[n_lintels - 1].z2 = sister;
+                               drop_l[n_lintels - 1].h = zones[i].h;
+                               drop_l[n_lintels - 1].valid = 1;
+                               
+                       }
+
+                       
+                       zones[sister].h--;
+                       zones[i].h--;
+               }
+       }
+       
+       return MY_MAX(points, points_max);
+}
+#endif
+
+
+/*  */
+int find_column_dropzone(uint8_t zones_num, zone* zones)
+{
+       uint8_t i;
+       
+       uint8_t z_n = 0;
+
+       if (zones_num <= 0)
+               return -1;
+
+       for (i = 0; i < zones_num; i++){
+               if (!zones[i].valid)
+                       continue;
+               if (zones[i].h > zones[z_n].h)
+                       z_n = i;
+       }
+
+
+       /* 
+          now, chose dropzone closest to robot 
+          meaning little x, big y
+          so maximise y-x
+        */
+       for (i = 0; i < zones_num; i++){
+               if (zones[i].h != zones[z_n].h)
+                       continue;
+               if (!zones[i].valid)
+                       continue;
+               if (zones[i].p.y - zones[i].p.x > zones[z_n].p.y - zones[z_n].p.x)
+                       z_n = i;
+       }
+       
+       
+       
+       return z_n;
+}
+
+
+
+uint8_t color2h(uint8_t color)
+{
+       return (0x100-color)/0x20;
+}
+
+uint8_t h2color(uint8_t color)
+{
+       return color*0x20;
+}
+
+
+#define NUM_ZONE_GENERATE 8
+#define DIST_ZONE_GEN 9
+
+/*
+  remove zone at ground level, and generate zones on 
+  a circle at X cm from center
+ */
+unsigned int generate_center_ground_zones(unsigned char* data, int16_t x_in, int16_t y_in,
+                                         zone * zones, unsigned int zones_num, uint8_t max_zones, int16_t center_x, int16_t center_y)
+{
+       double c_a, s_a;
+       uint8_t i, j;
+       double px1, py1, px2, py2;
+
+       
+       /* first del zone at level 2 */
+       for (i = 0; i < zones_num; ){
+               if (zones[i].h!=2){
+                       i++;
+                       continue;
+               }
+
+               for (j = i; j < zones_num-1; j++)
+                       zones[j] = zones[j+1];
+
+               zones_num--;
+
+       }
+
+       /* generate zones around circle  */
+
+       c_a = cos(2*M_PI/NUM_ZONE_GENERATE);
+       s_a = sin(2*M_PI/NUM_ZONE_GENERATE);
+
+       px1 = DIST_ZONE_GEN;
+       py1 = 0;
+
+       for (i = 0; i < NUM_ZONE_GENERATE; i++){
+               
+               zones[zones_num].p.x = center_x + px1;
+               zones[zones_num].p.y = center_y + py1;
+               zones[zones_num].h = 2;
+               zones[zones_num].valid = 1;
+
+               
+               px2 = px1*c_a + py1*s_a;
+               py2 = -px1*s_a + py1*c_a;
+
+               px1 = px2;
+               py1 = py2;
+
+               /* skip zone if it is not in img */
+               if (zones[zones_num].p.x < 0 ||  zones[zones_num].p.y < 0 ||
+                   zones[zones_num].p.x >= x_in ||  zones[zones_num].p.y > y_in)
+                       continue;
+
+               /* skip zone if not enougth pixels */
+               if (!zone_has_enought_pixels(data, x_in, y_in, &zones[zones_num]))
+                       continue;
+               
+               zones_num++;
+               if (zones_num >= max_zones)
+                       break;
+               
+       }
+
+       return zones_num;
+
+       
+}
+
+
+/*
+  remove zone at ground level, and generate zones on 
+  a line at X cm from robot
+ */
+unsigned int generate_rectangle_ground_zones(unsigned char* data, int16_t x_in, int16_t y_in,
+                                            zone * zones, unsigned int zones_num, uint8_t max_zones, int16_t center_x, int16_t center_y,
+                                            uint8_t working_zone)
+{
+       uint8_t i, j;
+       uint8_t y;
+
+       /* first del zone at level i */
+       for (i = 0; i < zones_num; ){
+               if (zones[i].h != working_zone ){
+                       i++;
+                       continue;
+               }
+
+               for (j = i; j < zones_num-1; j++)
+                       zones[j] = zones[j+1];
+
+               zones_num--;
+       }
+
+
+       /* generate zones on a line  */
+       for (y = OBJECT_DIM; y < y_in; y+=OBJECT_DIM){
+
+               zones[zones_num].p.x = center_x;
+               zones[zones_num].p.y = y;
+               zones[zones_num].h = working_zone ;
+               zones[zones_num].valid = 1;
+
+               if (!zone_has_enought_pixels(data, x_in, y_in, &zones[zones_num]))
+                       continue;
+               zones_num++;
+
+               if (zones_num >= max_zones)
+                       break;
+               
+       } 
+       return zones_num;
+       
+}
+
+
+#define MAX_DECAL_LINE 5
+#define ENOUGHT_ZONE_PIXEL 2
+
+void recal_img_y(unsigned char* buffer, int16_t x_in, int16_t y_in, 
+                uint8_t working_zone)
+{
+       uint8_t i, j;
+       uint8_t cpt;
+
+       /* recal img only for central zone */
+       if (working_zone !=2)
+               return;
+       
+       for (i = 0; i < MAX_DECAL_LINE; i++){
+               cpt = 0;
+               for (j = 0; j < x_in; j++){
+                       if (buffer[i*x_in + j] ==2)
+                               cpt++;
+               }
+
+               if (cpt >= ENOUGHT_ZONE_PIXEL)
+                       break;
+       }
+
+       memmove(buffer, &buffer[i * x_in], x_in * y_in - i*x_in);
+       memset(&buffer[x_in * y_in - i * x_in], 0,  i*x_in);
+}
+
+
+#define MAX_OBJECTS 20
+
+#define MAX_ZONES_PER_OBJECT 20
+
+
+uint8_t g_zone_num;
+zone g_all_zones[MAX_ZONES];
+
+uint8_t process_img(unsigned char *buffer, int16_t x_in, int16_t y_in,
+                   zone * all_zones, uint8_t max_zones)
+{
+
+       int ret;
+       int i, j;
+
+       zone zones[MAX_ZONES_PER_OBJECT];
+  
+       vect_t caliper;
+       unsigned int pts_cal[4];
+       point_t ptmp;
+       vect_t r1, r2;
+       int zone_len;
+  
+  
+       uint8_t zone_num = 0;
+
+       Object_bb sac_obj[MAX_OBJECTS];
+       Object_poly sac_obj_poly[MAX_OBJECTS];
+  
+
+
+       /* 
+          XXX fix: only decal for working zone 2/(1?) 
+          but we dont have info yet
+       */
+       recal_img_y(buffer, x_in, y_in, 2);
+
+       memset(sac_obj, 0, sizeof(sac_obj));
+       memset(sac_obj_poly, 0, sizeof(sac_obj_poly));
+
+
+       /* first, find polygons*/
+       parcour_img(buffer, x_in, y_in, sac_obj, sac_obj_poly, MAX_OBJECTS);
+
+       /* enclose each poygon in the min area polygon
+          then, split each rectangle in dropping zone
+       */
+       for (i=0;i<MAX_OBJECTS;i++){
+
+               if (!sac_obj_poly[i].pts_num)
+                       continue;
+               
+               IMG_DEBUG("obj: %d %d %d %d %d", 
+                         i, 
+                         sac_obj[i].x_min, 
+                         sac_obj[i].y_min, 
+                         sac_obj[i].x_max, 
+                         sac_obj[i].y_max);
+               
+               //IMG_DEBUG("poly pts_num: %d", sac_obj_poly[i].pts_num);
+        
+               object_poly_get_min_ar(&sac_obj_poly[i], &pts_cal[0], &caliper, &r1, &r2);
+    
+               ret = object_poly_caliper_to_rectangle(&sac_obj_poly[i], &pts_cal[0], &caliper,
+                                                      &r1, &r2, &ptmp);
+    
+               if (!ret)
+                       continue;
+
+               /*
+               IMG_DEBUG("r: (%3"PRIi32" %3"PRIi32")  "
+                            "(%3"PRIi32" %3"PRIi32")",
+                            r1.x, r1.y, r2.x, r2.y);
+               IMG_DEBUG("intersection: %"PRIi32" %"PRIi32"",
+                            ptmp.x, ptmp.y);
+               */
+    
+               zone_len = split_rectangle(&ptmp, &r1, &r2, 
+                                             MAX_ZONES_PER_OBJECT, &zones[0], sac_obj_poly[i].color);
+               //IMG_DEBUG("split ok %d", zone_len);
+    
+               zone_len =  zone_filter_min_surface(buffer, x_in, y_in, 
+                                                   sac_obj_poly[i].color, 
+                                                   zone_len, &zones[0]);               
+               
+               for (j = 0; j < zone_len && zone_num < max_zones; zone_num++, j++)
+                       all_zones[zone_num] = zones[j];
+
+       }
+
+
+       IMG_NOTICE("num zones end: %d", zone_num);
+       
+       return zone_num;
+}
+
+
+void process_img_to_zone(unsigned char *buffer, int16_t x_in, int16_t y_in)
+{
+       g_zone_num = process_img(buffer, x_in, y_in,
+                                g_all_zones, MAX_ZONES);
+}
+
+uint8_t filter_zones(unsigned char *buffer, int16_t x_in, int16_t y_in,
+                    zone * all_zones, uint8_t zone_num, uint8_t max_zones,
+                    uint8_t working_zone, int16_t center_x, int16_t center_y,
+                    int tweak_min_margin)
+{
+       uint8_t i;
+
+       /* first valid all zones */
+       for (i = 0; i < zone_num; i++){
+               all_zones[i].valid = 1;
+
+               /* filter zone lower thatn working zone */
+               if (all_zones[i].h  < working_zone)
+                       all_zones[i].valid = 0;
+       }
+
+
+       /* 
+          generate correct zone at ground level 
+          (depending on working zone)
+       */
+       if (working_zone == 2)
+               zone_num =  generate_center_ground_zones(buffer, x_in, y_in, 
+                                                        all_zones, zone_num, max_zones, center_x, center_y);
+       else
+               zone_num =  generate_rectangle_ground_zones(buffer, x_in, y_in, 
+                                                           all_zones, zone_num, max_zones, center_x, center_y,
+                                                           working_zone);
+
+       /* filter zone position, depending on workingzone */    
+       if (working_zone == 2)
+               zone_num = zone_filter_center(zone_num, all_zones, center_x, center_y, tweak_min_margin);
+       else
+               zone_num = zone_filter_zone_rect(zone_num, all_zones, center_x, center_y , working_zone);
+               
+
+
+       /* display zones (debug purpose) */
+
+       for (i = 0; i < zone_num; i++){
+               //buffer[all_zones[i].p.y*x_in+all_zones[i].p.x] = 0x3;
+               IMG_NOTICE("h:%d (v:%d) x:%"PRIi32" y:%"PRIi32"", all_zones[i].h, all_zones[i].valid, all_zones[i].p.x, all_zones[i].p.y);
+       }
+  
+       IMG_NOTICE("num zones: %d", zone_num);
+
+       
+       
+
+       return zone_num;
+}
+
+
+/*
+  return -1 if not column dropzone is found
+  return column hight if found
+*/
+int8_t get_column_dropzone(unsigned char *buffer, int16_t x_in, int16_t y_in, 
+                          uint8_t working_zone, int16_t center_x, int16_t center_y,
+                          int16_t * dropzone_x, int16_t * dropzone_y)
+{
+       uint8_t zone_num;
+       int c_drop_zone;
+
+
+
+       zone_num = filter_zones(buffer, x_in, y_in,
+                               g_all_zones, g_zone_num, MAX_ZONES,
+                               working_zone, center_x, center_y,
+                               0);
+
+       c_drop_zone = find_column_dropzone(zone_num, g_all_zones);
+
+       if (c_drop_zone<0)
+               return -1;
+
+       *dropzone_x = g_all_zones[c_drop_zone].p.x;
+       *dropzone_y = g_all_zones[c_drop_zone].p.y;
+
+       *dropzone_x = (*dropzone_x) * PIXEL2CM;
+       *dropzone_y = (*dropzone_y) * PIXEL2CM + 30;
+
+       return g_all_zones[c_drop_zone].h;
+}
+
+#define ROBOT_SEMI_INTERCOLUMN_SPACE 75
+
+uint8_t is_temple_there(unsigned char * buffer, int16_t x_in, int16_t y_in, 
+                       uint8_t h, int16_t center_x, int16_t center_y)
+{
+       zone z;
+       int ret;
+       int i;
+
+       
+       IMG_NOTICE("test z (mm) : x:%d y:%d", center_x, center_y);
+       IMG_NOTICE("test z:(pix): x:%d y:%d", (int)(center_x/ PIXEL2CM), (int)(center_y/ PIXEL2CM));
+       
+       
+       z.p.x = center_x / PIXEL2CM;
+       z.p.y = (center_y - ROBOT_SEMI_INTERCOLUMN_SPACE) / PIXEL2CM ;
+       z.h = h;
+       z.valid = 1;
+       ret = zone_has_enought_pixels(buffer, x_in, y_in, &z);
+       IMG_NOTICE("test z1: %d", ret);
+
+       if (!ret)
+               return 0;
+
+       z.p.x = center_x / PIXEL2CM;
+       z.p.y = (center_y  + ROBOT_SEMI_INTERCOLUMN_SPACE)/ PIXEL2CM;
+       z.h = h;
+       z.valid = 1;
+       ret = zone_has_enought_pixels(buffer, x_in, y_in, &z);
+       IMG_NOTICE("test z2: %d", ret);
+       if (!ret)
+               return 0;
+
+
+       /* 
+          if middle zone is less or egual to tested temple 
+        */
+       z.p.x = center_x / PIXEL2CM;
+       z.p.y = center_y / PIXEL2CM;
+       
+       for (i = h; i > 0; i--){
+               z.h = i;
+               z.valid = 1;
+               ret = zone_has_enought_pixels(buffer, x_in, y_in, &z);
+               IMG_NOTICE("test z3:(h=%d) %d", i, ret);
+               
+               if (ret)
+                       return 1;
+       }
+
+       return 0;
+
+}
+
+
+int8_t find_temple_dropzone(unsigned char *buffer, int16_t x_in, int16_t y_in, 
+                           uint8_t working_zone, int16_t center_x, int16_t center_y,
+                           int16_t * dropzone_x, int16_t * dropzone_y)
+{
+       uint8_t zone_num;
+       int8_t sisters[MAX_ZONES][MAX_SISTER_PER_ZONE];
+       int8_t z_n = -1;
+       uint8_t i, j;
+
+
+       /* find all drop zone */
+       zone_num = filter_zones(buffer, x_in, y_in,
+                               g_all_zones, g_zone_num, MAX_ZONES,
+                               working_zone, center_x, center_y,
+                               -2);
+
+       /* precompute possible twin towers */
+       find_twin_tower(zone_num, g_all_zones, sisters, center_x, center_y);
+       
+
+       for (i=0; i< zone_num; i++){
+               IMG_DEBUG("all sisters: %d", i);
+               for (j=0;j<MAX_SISTER_PER_ZONE;j++){
+                       IMG_DEBUG("s: %d", sisters[i][j]);
+               }
+       }
+
+       /* only use first sister of each zone */
+       for (i=0; i< zone_num; i++){
+               
+               /* if zone doesn't have twin tower */
+               if (sisters[i][0] == -1)
+                       continue;
+
+               if (!g_all_zones[i].valid)
+                       continue;
+
+               if (z_n == -1){
+                       z_n = i;
+                       continue;
+               }
+
+               /* if we found higher twin tower */
+               if (g_all_zones[z_n].h > g_all_zones[i].h)
+                       continue;
+
+               z_n = i;
+       }
+
+       IMG_NOTICE("twin tower found :z_n=%d", z_n);
+       if (z_n ==  -1)
+               return -1;
+       
+       IMG_NOTICE("(%"PRIi32" %"PRIi32") (%"PRIi32" %"PRIi32")", 
+                  g_all_zones[z_n].p.x, g_all_zones[z_n].p.y,
+                  g_all_zones[sisters[z_n][0]].p.x, g_all_zones[sisters[z_n][0]].p.y);
+       
+
+       *dropzone_x = ((double)(g_all_zones[z_n].p.x + g_all_zones[sisters[z_n][0]].p.x)*PIXEL2CM ) / 2;
+       *dropzone_y = ((double)(g_all_zones[z_n].p.y + g_all_zones[sisters[z_n][0]].p.y)*PIXEL2CM ) / 2 + 30;
+
+
+       return g_all_zones[z_n].h;
+
+}