Exemplo n.º 1
0
static void update_view(vx_gtk_buffer_manager_t * gtk)
{
    // This order of these two mutex locks should prevent deadloc
    // even if a user sends op codes while the user holds the GDK mutex
    gdk_threads_enter();
    pthread_mutex_lock(&gtk->mutex);

    // Clear XXX Double buffered?
    GList * children = gtk_container_get_children(GTK_CONTAINER(gtk->window));
    for(GList * iter = children; iter != NULL; iter = g_list_next(iter))
        gtk_widget_destroy(GTK_WIDGET(iter->data));
    g_list_free(children);

    // Rebuild from scratch
    GtkWidget * box = gtk_vbox_new(0, 10);
    GtkWidget * widget = NULL;

    zarray_t *layers = zhash_values(gtk->layers); // contents: layer_info_t*
    zarray_sort(layers, layer_info_compare);

    for (int lidx = 0; lidx < zarray_size(layers); lidx++) {

        layer_info_t *linfo = NULL;
        zarray_get(layers, lidx, &linfo);

        // Draw the layer name:
        widget = gtk_label_new("");
        char * text = sprintf_alloc("<b>Layer %d</b>", linfo->layer_id);
        gtk_label_set_markup(GTK_LABEL(widget), text);
        free(text);
        //gtk_container_add(GTK_CONTAINER(box), widget);
        gtk_box_pack_start(GTK_BOX(box), widget, FALSE, FALSE, 0);

        // Make a checkbox for each buffer
        zarray_t *buffers = zhash_values(linfo->buffers); // contents: buffer_info_t*
        zarray_sort(buffers, buffer_info_compare);

        for (int i = 0; i < zarray_size(buffers); i++) {

            buffer_info_t * buffer = NULL;
            zarray_get(buffers, i, &buffer);
            assert(buffer != NULL);

            widget = gtk_check_button_new_with_label(buffer->name);
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), buffer->enabled);
            g_signal_connect (G_OBJECT(widget), "toggled",
                              G_CALLBACK (buffer_checkbox_changed), buffer);
            //gtk_container_add(GTK_CONTAINER(box), widget);
            gtk_box_pack_start(GTK_BOX(box), widget, FALSE, FALSE, 0);
        }

        zarray_destroy(buffers);
    }
    gtk_container_add(GTK_CONTAINER(gtk->window), box);
    gtk_widget_show_all(box);

    zarray_destroy(layers);

    pthread_mutex_unlock(&gtk->mutex);
    gdk_threads_leave();
}
Exemplo n.º 2
0
// return 1 if the quad looks okay, 0 if it should be discarded
int fit_quad(apriltag_detector_t *td, image_u8_t *im, zarray_t *cluster, struct quad *quad)
{
    int res = 0;

    int sz = zarray_size(cluster);
    if (sz < 4) // can't fit a quad to less than 4 points
        return 0;

    /////////////////////////////////////////////////////////////
    // Step 1. Sort points so they wrap around the center of the
    // quad. We will constrain our quad fit to simply partition this
    // ordered set into 4 groups.

    // compute a bounding box so that we can order the points
    // according to their angle WRT the center.
    int xmax = 0, xmin = 9999999, ymax = 0, ymin = 9999999;

    for (int pidx = 0; pidx < zarray_size(cluster); pidx++) {
        struct pt *p;
        zarray_get_volatile(cluster, pidx, &p);

        xmax = imax(xmax, p->x);
        xmin = imin(xmin, p->x);

        ymax = imax(ymax, p->y);
        ymin = imin(ymin, p->y);
    }

    int cx = (xmin + xmax) / 2;
    int cy = (ymin + ymax) / 2;

    for (int pidx = 0; pidx < zarray_size(cluster); pidx++) {
        struct pt *p;
        zarray_get_volatile(cluster, pidx, &p);

        p->theta = atan2f(p->y - cy, p->x - cx);
    }

    zarray_sort(cluster, pt_compare_theta);

    // remove duplicate points. (A byproduct of our segmentation system.)
    if (1) {
        int outpos = 1;

        struct pt *last;
        zarray_get_volatile(cluster, 0, &last);

        for (int i = 1; i < sz; i++) {

            struct pt *p;
            zarray_get_volatile(cluster, i, &p);

            if (p->x != last->x || p->y != last->y) {

                if (i != outpos)  {
                    struct pt *out;
                    zarray_get_volatile(cluster, outpos, &out);
                    memcpy(out, p, sizeof(struct pt));
                }

                outpos++;
            }

            last = p;
        }

        cluster->size = outpos;
        sz = outpos;
    }

    if (sz < 4)
        return 0;

    /////////////////////////////////////////////////////////////
    // Step 2. Precompute statistics that allow line fit queries to be
    // efficiently computed for any contiguous range of indices.

    struct line_fit_pt *lfps = (struct line_fit_pt *)calloc(sz, sizeof(struct line_fit_pt));

    for (int i = 0; i < sz; i++) {
        struct pt *p;
        zarray_get_volatile(cluster, i, &p);

        if (i > 0) {
            memcpy(&lfps[i], &lfps[i-1], sizeof(struct line_fit_pt));
        }

        double W = 1;

        if (p->x > 0 && p->x+1 < im->width && p->y > 0 && p->y+1 < im->height) {
            int grad_x = im->buf[p->y * im->stride + p->x + 1] -
                im->buf[p->y * im->stride + p->x - 1];

            int grad_y = im->buf[(p->y+1) * im->stride + p->x] -
                im->buf[(p->y-1) * im->stride + p->x];

            W = sqrtf(grad_x*grad_x + grad_y*grad_y) + 1;
        }

        lfps[i].Mx  += W * p->x;
        lfps[i].My  += W * p->y;
        lfps[i].Mxx += W * p->x * p->x;
        lfps[i].Mxy += W * p->x * p->y;
        lfps[i].Myy += W * p->y * p->y;
        lfps[i].W   += W;
    }

    int indices[4];
    if (1) {
        if (!quad_segment_maxima(td, cluster, lfps, indices))
            goto finish;
    } else {
        if (!quad_segment_agg(td, cluster, lfps, indices))
            goto finish;
    }

//    printf("%d %d %d %d\n", indices[0], indices[1], indices[2], indices[3]);

    if (0) {
        // no refitting here; just use those points as the vertices.
        // Note, this is useful for debugging, but pretty bad in
        // practice since this code path also omits several
        // plausibility checks that save us tons of time in quad
        // decoding.
        for (int i = 0; i < 4; i++) {
            struct pt *p;
            zarray_get_volatile(cluster, indices[i], &p);

            quad->p[i][0] = p->x;
            quad->p[i][1] = p->y;
        }

        res = 1;

    } else {
        double lines[4][4];

        for (int i = 0; i < 4; i++) {
            int i0 = indices[i];
            int i1 = indices[(i+1)&3];

            if (0) {
                // if there are enough points, skip the points near the corners
                // (because those tend not to be very good.)
                if (i1-i0 > 8) {
                    int t = (i1-i0)/6;
                    if (t < 0)
                        t = -t;

                    i0 = (i0 + t) % sz;
                    i1 = (i1 + sz - t) % sz;
                }
            }

            double err;
            fit_line(lfps, sz, i0, i1, lines[i], NULL, &err);

            // XXX VALUE?
//             printf("%f %d\n", err, i1-i0);
            if (err > td->qtp.max_line_fit_mse) {
                res = 0;
                goto finish;
            }
        }

        for (int i = 0; i < 4; i++) {
            // solve for the intersection of lines (i) and (i+1)&3.
            // p0 + lambda0*u0 = p1 + lambda1*u1, where u0 and u1
            // are the line directions.
            //
            // lambda0*u0 - lambda1*u1 = (p1 - p0)
            //
            // rearrange (solve for lambdas)
            //
            // [u0_x   -u1_x ] [lambda0] = [ p1_x - p0_x ]
            // [u0_y   -u1_y ] [lambda1]   [ p1_y - p0_y ]
            //
            // remember that lines[i][0,1] = p, lines[i][2,3] = NORMAL vector.
            // We want the unit vector, so we need the perpendiculars. Thus, below
            // we have swapped the x and y components and flipped the y components.

            double A00 =  lines[i][3],  A01 = -lines[(i+1)&3][3];
            double A10 =  -lines[i][2],  A11 = lines[(i+1)&3][2];
            double B0 = -lines[i][0] + lines[(i+1)&3][0];
            double B1 = -lines[i][1] + lines[(i+1)&3][1];

            double det = A00 * A11 - A10 * A01;

            // inverse.
            double W00 = A11 / det, W01 = -A01 / det;
            if (fabs(det) < 0.001) {
                res = 0;
                goto finish;
            }

            // solve
            double L0 = W00*B0 + W01*B1;

            // compute intersection
            quad->p[i][0] = lines[i][0] + L0*A00;
            quad->p[i][1] = lines[i][1] + L0*A10;

            if (0) {
                // we should get the same intersection starting
                // from point p1 and moving L1*u1.
                double W10 = -A10 / det, W11 = A00 / det;
                double L1 = W10*B0 + W11*B1;

                double x = lines[(i+1)&3][0] - L1*A10;
                double y = lines[(i+1)&3][1] - L1*A11;
                assert(fabs(x - quad->p[i][0]) < 0.001 &&
                       fabs(y - quad->p[i][1]) < 0.001);
            }

            res = 1;
        }
    }

    // reject quads with edges that are too short or long.
    if (1) {
        for (int i = 0; i < 3; i++) {
            double dist2 = sq(quad->p[i][0] - quad->p[i+1][0]) +
                sq(quad->p[i][1] - quad->p[i+1][1]);

            if (dist2 < sq(6) || dist2 > sq(4096)) {
                res = 0;
                goto finish;
            }
        }
    }

    // reject quads whose cumulative angle change isn't equal to 2PI
    if (1) {
        double total = 0;

        for (int i = 0; i < 4; i++) {
            int i0 = i, i1 = (i+1)&3, i2 = (i+2)&3;

            double theta0 = atan2f(quad->p[i0][1] - quad->p[i1][1],
                                   quad->p[i0][0] - quad->p[i1][0]);
            double theta1 = atan2f(quad->p[i2][1] - quad->p[i1][1],
                                   quad->p[i2][0] - quad->p[i1][0]);

            double dtheta = theta0 - theta1;
            if (dtheta < 0)
                dtheta += 2*M_PI;

            if (dtheta < td->qtp.critical_rad || dtheta > (M_PI - td->qtp.critical_rad))
                res = 0;

            total += dtheta;
        }

        if (total < 6.2 || total > 6.4) {
            res = 0;
            goto finish;
        }
    }

    // adjust pixel coordinates; all math up 'til now uses pixel
    // coordinates in which (0,0) is the lower left corner. But each
    // pixel actually spans from to [x, x+1), [y, y+1) the mean value of which
    // is +.5 higher than x & y.
    for (int i = 0; i < 4; i++) {
        quad->p[i][0] += .5;
        quad->p[i][1] += .5;
    }

  finish:
/*
    if (res) {
        static image_u8_t *im;

        if (im == NULL)
            im = image_u8_create(1920,1080);

        for (int j = 0; j < 4; j++) {
            int i0 = indices[j];
            int i1 = indices[(j+1)&3];

            if (i1 < i0)
                i1 += sz;

            for (int i = i0; i <= i1; i++) {
                struct pt *p;
                zarray_get_volatile(cluster, i % sz, &p);
                im->buf[((int) p->y)*im->stride + ((int) p->x)] = 64 + 128*(j%2);
            }
        }

        char name[1024];
        sprintf(name, "debug_seg.pnm", utime_now());
        image_u8_write_pnm(im, name);
    }
*/

    free(lfps);

    return res;
}
Exemplo n.º 3
0
// this loop tries to run at X fps, and issue render commands
static void * render_loop(void * foo)
{
    state_t * state = foo;
    if (verbose)printf("Starting render thread!\n");

    uint64_t render_count = 0;
    uint64_t last_mtime = vx_mtime();
    double avgDT = 1.0f/state->target_frame_rate;
    uint64_t avg_loop_us = 3000; // initial render time guess
    while (state->rendering) {
        int64_t sleeptime = (1000000 / state->target_frame_rate) - (int64_t) avg_loop_us;
        if (sleeptime > 0)
            usleep(sleeptime); // XXX fix to include render time

        // Diagnostic tracking
        uint64_t mtime_start = vx_mtime(); // XXX
        avgDT = avgDT*.9 + .1 * (mtime_start - last_mtime)/1000;
        last_mtime = mtime_start;
        render_count++;

        if (verbose) {
            if (render_count % 100 == 0)
                printf("Average render DT = %.3f FPS = %.3f avgloopus %"PRIu64" sleeptime = %"PRIi64"\n",
                       avgDT, 1.0/avgDT, avg_loop_us, sleeptime);
        }

        // prep the render data
        render_buffer_t rbuf;
        rbuf.state = state;
        rbuf.width = gtku_image_pane_get_width(state->imagePane);
        rbuf.height = gtku_image_pane_get_height(state->imagePane);
        if (rbuf.width == 0 && rbuf.height == 0)
            continue; // if the viewport is 0,0

        // smartly reuse, or reallocate the output pixel buffer when resizing occurs
        GdkPixbuf * pixbuf = state->pixbufs[state->cur_pb_idx];
        if (pixbuf == NULL || gdk_pixbuf_get_width(pixbuf) != rbuf.width || gdk_pixbuf_get_height(pixbuf) != rbuf.height) {
            if (pixbuf != NULL) {
                g_object_unref(pixbuf);
                free(state->pixdatas[state->cur_pb_idx]);
            }

            state->pixdatas[state->cur_pb_idx] = malloc(rbuf.width*rbuf.height*3); // can't stack allocate, can be too big (retina)
            pixbuf = gdk_pixbuf_new_from_data(state->pixdatas[state->cur_pb_idx], GDK_COLORSPACE_RGB, FALSE, 8,
                                              rbuf.width, rbuf.height, rbuf.width*3, NULL, NULL); // no destructor fn for pix data, handle manually
            state->pixbufs[state->cur_pb_idx] = pixbuf;
        }

        // second half of init:
        rbuf.out_buf = gdk_pixbuf_get_pixels(pixbuf);
        rbuf.format = GL_RGB;
        rbuf.rendered = 0;

        // 1 compute all the viewports
        render_info_t * rinfo = render_info_create();
        rinfo->viewport[0] = rinfo->viewport[1] = 0;
        rinfo->viewport[2] = rbuf.width;
        rinfo->viewport[3] = rbuf.height;

        {
            zhash_iterator_t itr;
            uint32_t layer_id = 0;
            layer_info_t * linfo = NULL;
            zhash_iterator_init(state->layer_info_map, &itr);
            while(zhash_iterator_next(&itr, &layer_id, &linfo)){
                zarray_add(rinfo->layers, &linfo);
            }
            zarray_sort(rinfo->layers, zvx_layer_info_compare);
        }

        zarray_t * fp = zarray_create(sizeof(matd_t*));

        matd_t *mm = matd_create(4,4); zarray_add(fp, &mm);
        matd_t *pm = matd_create(4,4); zarray_add(fp, &pm);

        pthread_mutex_lock(&state->mutex);
        for (int i = 0; i < zarray_size(rinfo->layers); i++) {
            layer_info_t *linfo = NULL;
            zarray_get(rinfo->layers, i, &linfo);

            int * viewport = vx_viewport_mgr_get_pos(linfo->vp_mgr, rinfo->viewport, mtime_start);
            vx_camera_pos_t *pos = default_cam_mgr_get_cam_pos(linfo->cam_mgr, viewport, mtime_start);

            // store viewport, pos
            zhash_put(rinfo->layer_positions, &linfo->layer_id, &viewport, NULL, NULL);
            zhash_put(rinfo->camera_positions, &linfo->layer_id, &pos, NULL, NULL);

            // feed the actual camera/projection matrix to the gl side
            vx_camera_pos_model_matrix(pos,mm->data);
            vx_camera_pos_projection_matrix(pos, pm->data);

            matd_t * pmmm = matd_multiply(pm,mm); zarray_add(fp, &pmmm);

            float pm16[16];
            vx_util_copy_floats(pmmm->data, pm16, 16);

            float eye3[16];
            vx_util_copy_floats(pos->eye, eye3, 3);

            vx_gl_renderer_set_layer_render_details(state->glrend, linfo->layer_id, viewport, pm16, eye3);
        }

        // 2 Render the data
        task_thread_schedule_blocking(gl_thread, render_task, &rbuf);

        render_info_t * old = state->last_render_info;
        state->last_render_info = rinfo;

        pthread_mutex_unlock(&state->mutex);


        // 3 if a render occurred, then swap gtk buffers
        if (rbuf.rendered) {

            // point to the correct buffer for the next render:
            state->cur_pb_idx = (state->cur_pb_idx +1 ) % 2;

            // flip y coordinate in place:
            vx_util_flipy(rbuf.width*3, rbuf.height, rbuf.out_buf);

            // swap the image's backing buffer
            g_object_ref(pixbuf); // XXX Since gtku always unrefs with each of these calls, increment accordingly
            gtku_image_pane_set_buffer(state->imagePane, pixbuf);
        }

        // 3.1 If a movie is in progress, also need to serialize the frame
        pthread_mutex_lock(&state->movie_mutex);
        if (state->movie_file != NULL) {

            int last_idx = (state->cur_pb_idx + 1) % 2;
            GdkPixbuf * pb = state->pixbufs[last_idx];

            movie_frame_t * movie_img = calloc(1, sizeof(movie_frame_t));
            movie_img->mtime = mtime_start;
            movie_img->width = gdk_pixbuf_get_width(pb);
            movie_img->height = gdk_pixbuf_get_height(pb);
            movie_img->stride = 3*movie_img->width;
            movie_img->buf = malloc(movie_img->stride*movie_img->height);
            memcpy(movie_img->buf, state->pixdatas[last_idx], movie_img->stride*movie_img->height);


            // Alloc in this thread, dealloc in movie thread
            zarray_add(state->movie_pending, & movie_img);

            pthread_cond_signal(&state->movie_cond);

        }
        pthread_mutex_unlock(&state->movie_mutex);


        // cleanup
        if (old)
            render_info_destroy(old);
        zarray_vmap(fp, matd_destroy);
        zarray_destroy(fp);


        uint64_t mtime_end = vx_mtime();
        avg_loop_us = (uint64_t)(.5*avg_loop_us + .5 * 1000 * (mtime_end - mtime_start));
    }
    if (verbose) printf("Render thread exiting\n");

    pthread_exit(NULL);

}
Exemplo n.º 4
0
// returns the 35 points associated to the test chart in [x1,y1,x2,y2] 
// format if there are more than 35 points will return NULL
matd_t* build_homography(image_u32_t* im, vx_buffer_t* buf, metrics_t met)
{
    frame_t frame = {{0,0}, {im->width-1, im->height-1},
                        {0,0}, {1,1}};
    int good_size = 0;
    zarray_t* blobs = zarray_create(sizeof(node_t));
    hsv_find_balls_blob_detector(im, frame, met, blobs, buf);

    // remove unqualified blobs
    if(met.qualify) {
        for(int i = 0; i < zarray_size(blobs); i++) {
            node_t n;
            zarray_get(blobs, i, &n);

            if(!blob_qualifies(im, &n, met, buf))
                zarray_remove_index(blobs, i, 0);
        }
    }
    if(zarray_size(blobs) == NUM_TARGETS ||zarray_size(blobs) == NUM_CHART_BLOBS) good_size = 1;

    zarray_sort(blobs, compare);
    int pix_array[zarray_size(blobs)*2];

    // iterate through
    int idx = 0;
    double size = 2.0;
    for(int i = 0; i < zarray_size(blobs); i++) {
        node_t n;
        zarray_get(blobs, i, &n);
        loc_t center = {    .x = n.ave_loc.x/n.num_children,
                            .y = n.ave_loc.y/n.num_children};
        loc_t parent = {    .x = n.id % im->stride,
                            .y = n.id / im->stride};


        if(buf != NULL) {
            add_circle_to_buffer(buf, size, center, vx_maroon);
            // add_circle_to_buffer(buf, size, parent, vx_olive);

            // add_sides_to_buffer(im, buf, 1.0, &n, vx_orange, met);
            loc_t* lp = fit_lines(im, &n, buf, met, NULL);
            if(lp != NULL) {
                // printf("(%d, %d) (%d, %d) (%d, %d) (%d, %d) \n",
                //         lp[0].x, lp[0].y, lp[1].x, lp[1].y, lp[2].x, lp[2].y, lp[3].x, lp[3].y);
                loc_t intersect = get_line_intersection(lp[0], lp[1], lp[2], lp[3]);
                if(in_range(im, intersect.x, intersect.y)) {
                    loc_t ext_lines[2];
                    extend_lines_to_edge_of_image(im, intersect, center, ext_lines);
                    add_line_to_buffer(im, buf, 2.0, ext_lines[0], ext_lines[1], vx_blue);                
                }
                for(int i = 0; i < 4; i++) {
                    pix_array[i*2] = lp[i].x;
                    pix_array[i*2+1] = lp[i].y;
                    add_circle_to_buffer(buf, 3.0, lp[i], vx_orange);
                }
            }



            free(n.sides);

            // loc_t corners[4] = {{n.box.right, n.box.top},
            //                     {n.box.right, n.box.bottom},
            //                     {n.box.left, n.box.bottom},
            //                     {n.box.left, n.box.top}};
            // print extremes of box
            // if(1) {
            //     add_circle_to_buffer(buf, size, corners[0], vx_green);
            //     add_circle_to_buffer(buf, size, corners[1], vx_yellow);
            //     add_circle_to_buffer(buf, size, corners[2], vx_red);
            //     add_circle_to_buffer(buf, size, corners[3], vx_blue);
            //     for(int j = 0; j < 4; j++) {
            //         // add_circle_to_buffer(buf, size, corners[j], vx_maroon);
            //     }
            // }
        }
    }

    matd_t* H;
    H = dist_homography(pix_array, NUM_TARGETS);

    // if(0) {//zarray_size(blobs) == NUM_CHART_BLOBS){
    //     H = dist_homography(pix_array, NUM_CHART_BLOBS);
    // }
    // else if(zarray_size(blobs) == NUM_TARGETS){
    //     H = dist_homography(pix_array, NUM_TARGETS);
    //     if(met.add_lines) connect_lines(blobs, buf);
    // }
    // else {
    //     if(met.dothis)
    //         printf("num figures: %d\n", zarray_size(blobs));
    //     return(NULL);
    // }

    // make projected points
    // project_measurements_through_homography(H, buf, blobs, zarray_size(blobs));
    zarray_destroy(blobs);

    return(H);
}


/*
{ R00, R01, R02, TX,
   R10, R11, R12, TY,
   R20, R21, R22, TZ,
    0, 0, 0, 1 });
*/
double get_rotation(const char* axis, matd_t* H)
{
    double cosine, sine, theta;

    if(strncmp(axis,"x", 1)) {
        cosine = MATD_EL(H, 1, 1);
        sine = MATD_EL(H, 2, 1);
    }
    else if(strncmp(axis,"y", 1)) {
        cosine = MATD_EL(H, 0, 0);
        sine = MATD_EL(H, 0, 2);
    }
    else if(strncmp(axis,"z", 1)) {
        cosine = MATD_EL(H, 0, 0);
        sine = MATD_EL(H, 1, 0);
    }
    else assert(0);

    theta = atan2(sine, cosine);
    return(theta);
}

// if buf is NULL, will not fill with points of the homography
void take_measurements(image_u32_t* im, vx_buffer_t* buf, metrics_t met)
{
    // form homography
    matd_t* H = build_homography(im, buf, met);
    if(H == NULL) return;

    // get model view from homography
    matd_t* Model = homography_to_pose(H, 654, 655, 334, 224);
    // printf("\n");
    // matd_print(H, matrix_format);
    // printf("\n\n");
    // printf("model:\n");
    // matd_print(Model, "%15f");
    // printf("\n\n");
    // matd_print(matd_op("M^-1",Model), matrix_format);
    // printf("\n");
    // extrapolate metrics from model view
    double TX = MATD_EL(Model, 0, 3);
    double TY = MATD_EL(Model, 1, 3);
    double TZ = MATD_EL(Model, 2, 3);

    // double rot_x = get_rotation("x", H);
    // double rot_y = get_rotation("y", H);
    // double rot_z = get_rotation("z", H);

    double cosine = MATD_EL(Model, 0, 0);

    double rot_z = acos(cosine) * 180/1.5 - 180;


    cosine = MATD_EL(Model, 2, 2);
    double rot_x = asin(cosine) * 90/1.3 + 90;

    cosine = MATD_EL(Model, 1, 1);
    double rot_y = asin(cosine);



    char str[200];
    sprintf(str, "<<#00ffff,serif-30>> DIST:%lf  Offset:(%lf, %lf)\n rot: (%lf, %lf, %lf)\n", 
                TZ, TX, TY, rot_x, rot_y, rot_z);
    vx_object_t *text = vxo_text_create(VXO_TEXT_ANCHOR_BOTTOM_LEFT, str); 
    vx_buffer_add_back(buf, vxo_pix_coords(VX_ORIGIN_BOTTOM_LEFT, text));

    // printf("dist: %lf   cos:%lf  angle: %lf\n", TZ, cosine, theta);
}