Search code examples
callbackoctave

Using a Callback to identify a mouse selected point on a 3-D plot, sometimes only works inside another m-file as a function, why?


Working on the "Identifying a mouse selected point on a 3-D plot" problem the Callback function is used and recommended to retain interactive focus on a plot and select/identify any number of points, I ran into the following odd behaviour.

The only Matlab "answer" that uses Euclidian calculations erroneously, so I adapted it for Octave and I supply a better 3-D minimum-area solution here as complete code.

Something I couldn't figure out is when I tried to copy the code into a Fuzzy logic file, non-function, m-file it failed to work throwing errors at the Pt = get(handle.a, "CurrentPoint") ==> "handle is not recognised..." It matters nothing where I put the handle.a=axes declaration.

The version provided here works just as well with the M-file as a function or not but as soon as I put it in a file with other code more than just simple vector statements, it fails if the main file is not a "function-m-file"

I made it work out of pure guess work and added the "function" to the top of the file and saved it, can someone explain why?

function Interactive_surf

format short;
handle.a = axes;

Xt = [0 0.71429 1.4286  2.1429  2.8571  3.5714  4.2857  5   5.7143  6.4286  7.1429  7.8571  8.5714  9.2857  10];
Yt = [0 0.71429 1.4286  2.1429  2.8571  3.5714  4.2857  5   5.7143  6.4286  7.1429  7.8571  8.5714  9.2857  10];
Z = [ 6.2518    6.4574  7.0428  8.1286  9.3374  10.149  10.435  10.462  10.435  10.531  11.293  12.839  14.343  14.955  15;
      7.0713    7.2578  7.7689  8.7243  9.7992  10.528  10.786  10.81   10.786  10.542  11.306  12.856  14.362  14.971  15.015;
      8.2585    8.432   9.0076  9.789   10.672  11.272  11.486  11.506  11.486  11.272  11.516  13.118  14.652  15.218  15.247;
      9.7942    9.9353  10.715  12.178  12.698  13.041  13.161  13.172  13.161  13.041  12.698  14.456  16.12   16.419  16.386;
      11.446    11.556  12.295  14.137  16.275  16.06   15.987  15.98   15.987  16.06   16.275  17.449  19.288  18.78   18.699;
      12.902    12.988  13.682  15.392  17.287  18.291  18.598  18.572  18.598  18.877  19.709  21.084  22.69   21.138  21.166;
      13.936    14.007  14.664  16.27   17.992  18.872  19.229  19.401  19.481  19.815  20.77   22.286  23.469  22.158  22.181;
      14.532    14.594  15.229  16.77   18.394  19.207  19.537  19.703  19.781  20.116  21.061  22.519  23.532  22.807  22.827;
      14.819    14.878  15.5    17.01   18.586  19.368  19.686  19.848  19.925  20.26   21.198  22.625  23.602  23.454  23.469;
      14.937    14.993  15.611  17.108  18.665  19.434  19.747  19.907  19.984  20.319  21.253  22.667  23.962  24.001  24.011;
      14.979    15.035  15.652  17.143  18.694  19.457  19.769  19.929  20.005  20.34   21.273  22.682  23.97   24.394  24.396;
      14.995    15.051  15.667  17.157  18.704  19.466  19.777  19.937  20.013  20.348  21.281  22.688  23.973  24.651  24.651;
      15    15.056  15.671  17.161  18.707  19.469  19.78   19.939  20.016  20.351  21.283  22.69   23.974  24.674  24.803;
      15    15.056  15.671  17.161  18.707  19.469  19.78   19.939  20.016  20.351  21.283  22.69   23.974  24.674  24.89;
      15    15.056  15.671  17.161  18.707  19.469  19.78   19.939  20.016  20.351  21.283  22.69   23.974  24.674  24.922];

## Assign raw data to the handle structure
handle.x = Xt;
handle.y = Yt;
handle.z = Z;   ## NOTE the Z-matrix has columns as X-axisvalues and


###  Plot in 3D with CallBack function called
handle.p = surf(handle.x,handle.y,handle.z, 'ButtonDownFcn', {@click});
xlabel('x-axis');
ylabel('y-axis');
zlabel('z-axis');
grid on

function click(src,~)
    ## Get current perpendicular screen vector
    Pt = get(handle.a, "CurrentPoint")

    ###  Sort through every point in the surface to find the smallest Area to the point
    for dy= 1:15
      for dx = 1:15
        surf_Pt(dx, :) = [handle.x(dx) handle.y(dy) handle.z(dy, dx)];

        v1 = surf_Pt(dx, :) - Pt(1,:);         ## Position vectors: line vector end point to
        v2 = surf_Pt(dx, :) - Pt(2,:);         ## existing surface grid points
        X_Prod = cross(v1, v2);               ## Vector Cross product gives normal vector
        Mag_X_Prod = norm(X_Prod);            ## |normal vector|==> Area of //pipehead
        Area(dx, dy)  = 0.5*Mag_X_Prod;       ## Store each area of triangle
      endfor
    endfor

    [row, col] = find(Area == min(Area(:)))
    str = 'min_Area = %d';
    str1 = sprintf(str, Area(row, col));
    disp(str1)

    # Display the selected point on 3D plot.
    # Points on the fence may not generate a label,
    # so rotate the image so as it shows inside the grid, then click.
    
    formatSpec = "Pt clicked = [%d %d %d]";
    point_text_val = [handle.x(row) handle.y(col) handle.z(col, row)];
    pos_text_z = (handle.z(col, row))+0.2;
    str = sprintf(formatSpec, point_text_val)
    text(handle.x(row), handle.y(col), pos_text_z, str,'FontSize',18)

    ## NOTES
    ## The figure properties "alphamap" would be used to modify the transparency
    ## of the axes frame and allow visiblity of the labels added above that
    ## run off behind the 100%-white fence SADLY
    ##  "Transparency is not yet implemented for figure objects. alphamap is unused"


endfunction


end

Solution

  • In the code as posted, click is a nested function. As a nested function, it has access to the variables defined in the parent function.

    If you remove the function line at the top of the file, then the M-file is a script, and the function click is a local function. As a local function, it does not have access to variables defined outside of itself. Any variables defined in the script before or after the function are not visible inside the function.

    But note that Octave doesn’t allow local functions at the end of script M-files, and so click will not be defined when used. In MATLAB it would work, but not in Octave. In Octave you’d have to move the function declaration to some point in the script before you use it.

    In this form, you’d have to pass the handle into the function as an argument. For example you could define

    function click(handle,src)
    

    And then instead of @click, the handle you pass as a callback would be @(src,~)click(handle,src).