function [img_plate, u_axis, v_axis] = qrcode_target(grid_dimension, grid_spacing, grid_units)
    % img = qrcode_target(grid_dimension, grid_spacing, grid_units)
    %
    % generate a chessboard calibration target with a QR code fiduciary marker
    % four QR codes are placed at each corner of the chessboard
    %
    % calibration target images have QR codes to mark the coordinate of the corner to the bottom left of the code
    % the code also specifies the dimension of the grid in (u,v) as well as the chessboard
    % dimension and units
    %
    % the QRcode format is
    % version, u0, v0, n_u, n_v, du, dv, units
    % where
    %   version character string identifying version of QR code
    %   u0      u coordinate of corner to the bottom left of the code
    %   v0      v coordinate of the corner to the bottom left of the code
    %   n_u     number of chessboard tiles in the u direction
    %   n_v     number of chessboard tiles in the v direction
    %   du      spacing in u direction
    %   dv      spacing in v direction
    %   units   string identifying units of measurement
    
    %% generate QR code fiduciary markers
    qrcode_ary      = cell(1,4);
    corners         = [0 0;                     % bottom left
                       grid_dimension(1)-1 0;   % bottom right
                       0 grid_dimension(2)-1;   % top left
                       grid_dimension(1)-1 grid_dimension(2)-1];% top right
    corners         = corners - grid_dimension(:).'/2;
    for c = 1 : 4
        msg         = qrcode_format_string(corners(c,:), grid_dimension, grid_spacing*[1 1], grid_units);
        qrcode_ary{c} = ~encode_qr(msg, [1 1]);
    end
    % internal super-resolution factor to apply
    grid_resolution = size(qrcode_ary{1}, 1)*10;

    %% define image of calibration target
    % define coordinate system of calibration plate
    % checkerboard target has coordinate system (u,v,w)
    % u is horizontal, v is vertical, w is out of plane
    n_u              = grid_dimension(1) * grid_resolution + 1;
    n_v              = grid_dimension(2) * grid_resolution + 1;
    u_axis           = linspace(-grid_dimension(1)/2, grid_dimension(1)/2, n_u) * grid_spacing;
    v_axis           = linspace(-grid_dimension(2)/2, grid_dimension(2)/2, n_v) * grid_spacing;
    u_axis           = 0.5*(u_axis(1:end-1)+u_axis(2:end))';
    v_axis           = 0.5*(v_axis(1:end-1)+v_axis(2:end));
    [umat,vmat]      = ndgrid(u_axis, v_axis);
    
    % define the "image" of the calibration plate
    % this is a checkerboard pattern with the square at (0.5,0.5) being white
    uhat             = u_axis/grid_spacing + grid_dimension(1)/2;
    vhat             = v_axis/grid_spacing + grid_dimension(2)/2;
    ru               = mod(uhat, 2) < 1; % true
    rv               = mod(vhat, 2) < 1;
    img_plate        = xor(ru,rv);
    img_plate        = double(~img_plate);
    
    % add a fiduciary markers in the form of QR code
    for c = 1 : 4
        u0              = corners(c,1)*grid_spacing;
        v0              = corners(c,2)*grid_spacing;
        qrcode          = qrcode_ary{c};
        u_qr            = linspace(u0, u0+grid_spacing, size(qrcode, 1));
        v_qr            = linspace(v0, v0+grid_spacing, size(qrcode, 2));
        img_qr_big      = interpn(u_qr, v_qr, qrcode, umat, vmat, 'nearest', 1);
        img_plate       = and(img_plate, img_qr_big);
    end
end