diff --git a/PIV_2D_wdef.m b/PIV_2D_wdef.m index fedce4c24696491ee07846b641514c316ac12d6a..1dfd7cffa70f17b131cfd5f82c571f2886989766 100644 --- a/PIV_2D_wdef.m +++ b/PIV_2D_wdef.m @@ -152,6 +152,11 @@ function piv_result = PIV_2D_wdef(A, B, mask, piv_setup) % window size: we need to size this filter relative to the cutoff frequency ksize_filt = round(wsize_old ./ win_spacing_old) + 1; sd = sqrt(prod(ksize_filt))/3*0.65; + % median filter to avoid errors due to spurious vectors in + % result + %delta_ab_old(:,:,1) = medfilt2(delta_ab_old(:,:,1), ksize_filt); + %delta_ab_old(:,:,2) = medfilt2(delta_ab_old(:,:,2), ksize_filt); + % smoothing filter G_smooth = fspecial('gaussian', ksize_filt, sd); delta_ab_old = convn(delta_ab_old, G_smooth, 'same'); @@ -284,9 +289,19 @@ function piv_result = PIV_2D_wdef(A, B, mask, piv_setup) % mark bad vectors as NaN ux_mat(nan_mask) = nan; uy_mat(nan_mask) = nan; - % inpaint - ux_mat = inpaint_nans(ux_mat,1); - uy_mat = inpaint_nans(uy_mat,1); + + if true % pass == 1 + % inpaint NaN values on first pass + ux_mat = inpaint_nans(ux_mat,1); + uy_mat = inpaint_nans(uy_mat,1); + else + % subsequent passes just us previous predictor field + ux_pred = delta_ab_pred(:,:,1); + uy_pred = delta_ab_pred(:,:,2); + ux_mat(nan_mask)= ux_pred(nan_mask); + uy_mat(nan_mask)= uy_pred(nan_mask); + end + peak_choice(nan_mask)= 0; Q(nan_mask) = 0; peak_mag(nan_mask) = 0; diff --git a/calibration/chessboard_calibration.m b/calibration/chessboard_calibration.m new file mode 100644 index 0000000000000000000000000000000000000000..61e3f5739ffd8a270633b1d31df66cb706aaccf1 --- /dev/null +++ b/calibration/chessboard_calibration.m @@ -0,0 +1,250 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% dotboard_calibration.m +% find markers in images of calibration plates +% then perform a 2D calibration to dewarp the calibration plate image + +addpath([pwd '/../minmax/']); +addpath([pwd '/../pinhole/']); +addpath([pwd '/../misc/']); + +%% input parameters + +grid_dx = -1; % real units, mm +grid_dy = +1; +invert_image = 0; % invert image for black dots on white +blur_image = 0; % gaussian blur image +LoG_image = 0; % apply laplacian of Gaussian filter +checkerboard_image = 0; % apply checkerboard image filter +median_filter = 1; +median_filter_size = [5 5]; +normalise_image = 1; +corner_metric = 0.1; % threshold for identifying corners +dot_half_window = [64 64]; + + +output_dir = 'E:\mixer\lpt\calibration\21-06-14\'; +input_file_list = {'E:\mixer\lpt\calibration\21-06-14\C15794_im02.tif'}; +plate_origin = [0 0 0]'; +camera_index = 1; +camera_id = 1; +calib_idx_offset = 0; +origin_pos = [0 0]; + +image_clim = [0 255]; + +%% generate calibration for each image + +nCalibrations = length(input_file_list); + +calib_idx = 1; + +while calib_idx <= nCalibrations + %% update user of status + input_file = input_file_list{calib_idx}; + output_file = sprintf('%s/CAM%d-%02d.mat', output_dir, camera_index, calib_idx+calib_idx_offset); + fprintf('Image %02d of %02d:\n', calib_idx, nCalibrations); + + % check if calibration already exists + if exist(output_file, 'file') + sInput = input('Calibration already exists - overwrite? y to overwrite: ', 's'); + if ~strcmpi(sInput, 'y') + calib_idx = calib_idx + 1; + continue; + end + end + % check if image input exists + if ~exist(input_file, 'file') + fprintf(' - could not find image file\n'); + end + + %% load image + im = readimg(input_file); + im = double((im{1}')); + Nx = size(im, 1); + Ny = size(im, 2); + i_axis = 0 : Nx-1; + j_axis = 0 : Ny-1; + + %% create filtered image for processing + im_filt = im; + if invert_image + im_filt = max(im(:))-im; + end + if blur_image + G = fspecial('gaussian', [5 5]); + im_filt = filter2(G, im_filt); + end + if LoG_image + scale = norm(dot_half_window)*0.2; + G = fspecial('log', 2*dot_half_window, scale); + im_filt = scale * conv2(G, im_filt); + end + if median_filter + im_filt = medfilt2(im_filt, median_filter_size); + end + if checkerboard_image + G = repelem(eye(2), dot_half_window(1), dot_half_window(2)); + im_filt = conv2(G, im_filt); + end + if normalise_image + [minv, maxv] = minmaxfiltnd(single(im_filt), dot_half_window*2+1); + im_filt = (im_filt - minv) ./ max(16, maxv-minv); + end + + %% display image + figure(1); + clf; + hold off; + ax=gca; + h_filt = imagesc(im_filt'); + axis equal tight xy; + colormap gray; + hold on; + colorbar; + + %% crop image + fprintf('Select the bottom left square of the board:\n'); + im_bl = ginput(1); + plot(im_bl(1), im_bl(2), 'ro'); + fprintf('Select the bottom right square of the board:\n'); + im_br = ginput(1); + plot(im_br(1), im_br(2), 'ro'); + fprintf('Select the top right square of the board:\n'); + im_tr = ginput(1); + plot(im_tr(1), im_tr(2), 'ro'); + fprintf('Select the top left square of the board:\n'); + im_tl = ginput(1); + plot(im_tl(1), im_tl(2), 'ro'); + % identify corners + im_corners = [im_bl; im_tr]; + im_corners = sort(round(im_corners), 1); + + %% coordinate directions + im_ex = im_br + im_tr - im_bl - im_tl; + im_ey = im_tr + im_tl - im_br - im_bl; + im_ex = im_ex' / norm(im_ex); + im_ey = im_ey' / norm(im_ey); + + %% display cropped and filtered image + plot(ax(1), im_corners([1 2 2 1 1], 1), im_corners([1 1 2 2 1], 2), 'r-'); + rx = i_axis > im_corners(1,1) & i_axis < im_corners(2,1); + ry = j_axis > im_corners(1,2) & j_axis < im_corners(2,2); + % crop filtered image + im_crop = zeros(Nx,Ny); + im_crop(rx,ry) = im_filt(rx,ry); + im_filt = im_crop; + set(h_filt, 'cdata', im_filt'); + drawnow; + + %% extract markers using chessboard algorithm + h_markers = []; + h_text = []; + while ~isnan(corner_metric) + %% search for chessboard corners + fprintf('searching for checkerboard corners with m=%0.3f ...\n', corner_metric); + [ij_markers, board_size] = detectCheckerboardPoints(im_filt', 'MinCornerMetric', corner_metric); + ij_markers = ij_markers'; % 2 x n_markers + + %% update figure or try again + if isempty(ij_markers) + fprintf('no valid markers found.\n'); + fprintf('try again with a different threshold\n\n'); + else + %% display + figure(1); + hold on; + delete(h_markers); + delete(h_text); + h_markers = plot(ax(1), ij_markers(1,:), ij_markers(2,:), 'g+'); + end + + %% prompt for new threshold + ret = input('enter a new corner metric or nan to accept: ', 's'); + corner_metric = str2double(ret); + end + + %% get coordinates of markers + % create grid + x_grid = (1 : board_size(2)-1) * grid_dx; + y_grid = (1 : board_size(1)-1) * grid_dy; + x_grid = x_grid - round(mean(x_grid)); + y_grid = y_grid - round(mean(y_grid)); + [xm, ym] = meshgrid(x_grid, y_grid); + xy_markers = [xm(:) ym(:)]'; + n_markers = prod(board_size-1); + + % annotate + h_text = []; + delete(findobj(gcf,'type','text')); + for k = [1 board_size(1)-1 n_markers n_markers-board_size(1)+2] + str = sprintf('(%0.1f,%0.1f)', xy_markers(:,k)); + h_text(k) = text(ij_markers(1,k), ij_markers(2,k), str, 'color', 'b', ... + 'fontsize', 18, ... + 'horizontalalignment', 'center', 'verticalalignment', 'middle'); + end + + %% create calibration and dewarp + xy_tilde = [xy_markers; ones(1,n_markers)]; + P2D = dlt33([ij_markers;ones(1,n_markers)], xy_tilde); + ij_tilde = P2D * xy_tilde; + lambda = [1; 1] * ij_tilde(3,:); + ij_fit = ij_tilde(1:2,:) ./ lambda; + + ij_error = ij_markers - ij_fit; + px_error = sqrt(ij_error(1,:).^2 + ij_error(2,:).^2); + rms_error = sqrt(mean(px_error.^2)); + + fprintf('RMS fit error: %0.2f px\n', rms_error); + + %% work out x and y axes + x_lim = sort(x_grid([1 end])); + y_lim = sort(y_grid([1 end])); + x_axis = linspace(x_lim(1), x_lim(2), Nx); + y_axis = linspace(y_lim(1), y_lim(2), Ny); + % calculate dewarping + [xmat, ymat] = ndgrid(x_axis, y_axis); + ij_tilde_dewarped = P2D * [xmat(:) ymat(:) ones(Nx*Ny,1)]'; + lambda = ij_tilde_dewarped(3,:); + imat = reshape(ij_tilde_dewarped(1,:) ./ lambda, [Nx Ny]); + jmat = reshape(ij_tilde_dewarped(2,:) ./ lambda, [Nx Ny]); + im_xy = interp2(i_axis, j_axis, im', imat, jmat, 'linear'); + + %% show dewarped image + figure(2); + hold off; + imagesc(x_axis, y_axis, im_xy'); + xlim(x_lim); + ylim(y_lim); + colormap gray; + axis xy equal tight; + hold on; + %set(gca,'Clim', image_clim); + for x = (round(x_lim(1)/abs(grid_dx)) : round(x_lim(end)/abs(grid_dx))) * abs(grid_dx) + plot([x x], [y_axis(1) y_axis(end)], 'r-'); + end + for y = (round(y_lim(1)/abs(grid_dy)) : round(y_lim(end)/abs(grid_dy))) * abs(grid_dy) + plot([x_axis(1) x_axis(end)], [y y], 'r-'); + end + + %% query user to save calibration or repeat + sInput = input('Would you like to repeat the calibration? y to repeat: ', 's'); + if strcmpi(sInput, 'y') + continue; % continue without incrementing the counter + end + + %% save output + fprintf('Saving ... '); + fstruct = struct('source_image', im, ... + 'dewarped_image', im_xy, ... + 'plate_pos', plate_origin(:, calib_idx), ... + 'P', P2D, ... + 'i_axis', i_axis, 'j_axis', j_axis, ... + 'x_axis', x_axis, 'y_axis', y_axis, 'x_lim', x_lim, 'y_lim', y_lim, ... + 'grid_dx', grid_dx, 'grid_dy', grid_dy, ... + 'ij_markers', ij_markers, 'xy_markers', xy_markers, ... + 'camera_index', camera_index, ... + 'id', camera_id); + save(output_file, '-struct', 'fstruct'); + fprintf('done\n'); + calib_idx = calib_idx + 1; +end \ No newline at end of file diff --git a/calibration/dotboard_calibration.m b/calibration/dotboard_calibration.m new file mode 100644 index 0000000000000000000000000000000000000000..b800705b8c138f950a4cbef668ff3a2c9a6d0d00 --- /dev/null +++ b/calibration/dotboard_calibration.m @@ -0,0 +1,222 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% dotboard_calibration.m +% find markers in images of calibration plates +% then perform a 2D calibration to dewarp the calibration plate image + +addpath([pwd '/../../mex/minmax/']); +addpath([pwd '/pinhole/']); + +%% input parameters + +grid_dx = 20; % real units, mm +grid_dy = 20; +invert_image = 1; % invert image for black dots on white +blur_image = 0; % gaussian blur image +LoG_image = 0; % apply laplacian of Gaussian filter +normalise_image = 0; + + +output_dir = 'M:\mixer\calibration\20-11-17'; +input_file_list = { 'M:\mixer\calibration\20-11-17\B00001.tif'}; +plate_origin = [0 0 0]'; +camera_index = 1; +camera_id = 1; +dot_half_window = [32 32]; +calib_idx_offset = 0; +origin_pos = [0 0]; + +image_clim = [0 255]; + +%% generate calibration for each image + +nCalibrations = length(input_file_list); + +calib_idx = 1; + +while calib_idx <= nCalibrations + %% update user of status + input_file = input_file_list{calib_idx}; + output_file = sprintf('%s/CAM%d-%02d.mat', output_dir, camera_index, calib_idx+calib_idx_offset); + fprintf('Image %02d of %02d:\n', calib_idx, nCalibrations); + + % check if calibration already exists + if exist(output_file, 'file') + sInput = input('Calibration already exists - overwrite? y to overwrite: ', 's'); + if ~strcmpi(sInput, 'y') + calib_idx = calib_idx + 1; + continue; + end + end + % check if image input exists + if ~exist(input_file, 'file') + fprintf(' - could not find image file\n'); + end + + %% load image, request position + im = readimg(input_file); + %im = double(im{2}'); + im = rot90(im{2},-1); + Nx = size(im, 1); + Ny = size(im, 2); + + %% create filtered image for processing + im_filt = im; + if invert_image + im_filt = max(im(:))-im; + end + if blur_image + G = fspecial('gaussian', [5 5]); + im_filt = filter2(G, im_filt); + end + if LoG_image + scale = norm(dot_half_window)*0.2; + G = fspecial('log', 2*dot_half_window, scale); + im_filt = scale * conv2(G, im_filt); + end + if normalise_image + [minv, maxv] = minmaxfiltnd(single(im_filt), dot_half_window*2+1); + im_filt = (im_filt - minv) ./ max(16, maxv-minv); + end + + %% display image + figure(1); + hold off; + imagesc(im_filt'); + axis equal tight xy; + colormap gray; + %set(gca,'Clim', image_clim); + hold on; + colorbar; + + %% select region of interest + fprintf('Please select region of interest'); + [i_coord, j_coord] = ginput(2); + xlim(i_coord); + ylim(j_coord); + + %% get six marker positions + % center, first right, far right, far corner, far above, above + bDone = false; + while ~bDone + %% get marker position from user + fprintf('Please identify the six markers\n'); + fprintf('Press any key to abort and try again\n'); + + i_marker = nan(1,6); + j_marker = nan(1,6); + b_aborted = false; + h_plot = []; + for mark = 1 : 6 + %% get marker position from user input + [i_marker(mark) j_marker(mark), btn] = ginput(1); + if btn > 3 + b_aborted= true; + break; + end + %% get barycentre of marker, to be more accurate + dot_img_i = round(i_marker(mark)) + (-dot_half_window(1) : dot_half_window(1)); + dot_img_j = round(j_marker(mark)) + (-dot_half_window(2) : dot_half_window(2)); + [imat,jmat] = ndgrid(dot_img_i, dot_img_j); + dot_img = im_filt(dot_img_i, dot_img_j); + i_marker(mark) = sum(dot_img(:) .* imat(:)) / sum(dot_img(:)); + j_marker(mark) = sum(dot_img(:) .* jmat(:)) / sum(dot_img(:)); + + %% show user what he's plotting + delete(h_plot); + h_plot = plot(i_marker, j_marker, 'ro-'); + for it = 1 : mark + plot( i_marker(it)+[-1 -1 +1 +1 -1]*dot_half_window(1), ... + j_marker(it)+[-1 +1 +1 -1 -1]*dot_half_window(2), 'g-'); + end + end + if b_aborted + delete(h_plot); + continue; + end + bDone = true; + end + + %% find marker positions + [ij_markers, xy_markers]= find_markers(im_filt, [i_marker;j_marker], dot_half_window); + xy_markers(1,:) = xy_markers(1,:) * grid_dx + origin_pos(1); + xy_markers(2,:) = xy_markers(2,:) * grid_dy + origin_pos(2); + n_markers = size(xy_markers,2); + xy_tilde = [xy_markers; ones(1,n_markers)]; + + %% create calibration and dewarp + P2D = dlt33([ij_markers;ones(1,n_markers)], xy_tilde); + ij_tilde = P2D * xy_tilde; + lambda = [1; 1] * ij_tilde(3,:); + ij_fit = ij_tilde(1:2,:) ./ lambda; + + ij_error = ij_markers - ij_fit; + px_error = sqrt(ij_error(1,:).^2 + ij_error(2,:).^2); + rms_error = sqrt(mean(px_error.^2)); + + fprintf('RMS fit error: %0.2f px\n', rms_error); + + % work out x and y axes + ij_corners = [0 0 Nx Nx; + 0 Ny Ny 0]; + ij_tilde = [ij_corners; 1 1 1 1]; + xy_tilde = P2D^-1 * ij_tilde; + lambda = xy_tilde(3,:); + xy_corners = [xy_tilde(1,:) ./ lambda; + xy_tilde(2,:) ./ lambda]; + x_lim = sort(xy_corners(1,:), 'ascend'); + y_lim = sort(xy_corners(2,:), 'ascend'); + x_lim = x_lim([1 4]); + y_lim = y_lim([1 4]); + + x_axis = linspace(x_lim(1), x_lim(2), Nx); + y_axis = linspace(y_lim(1), y_lim(2), Ny); + i_axis = 1 : Nx; + j_axis = 1 : Ny; + % calculate dewarping + [xmat, ymat] = ndgrid(x_axis, y_axis); + ij_tilde_dewarped = P2D * [xmat(:) ymat(:) ones(Nx*Ny,1)]'; + lambda = ij_tilde_dewarped(3,:); + imat = reshape(ij_tilde_dewarped(1,:) ./ lambda, [Nx Ny]); + jmat = reshape(ij_tilde_dewarped(2,:) ./ lambda, [Nx Ny]); + im_xy = interp2(i_axis, j_axis, im', imat, jmat, 'linear'); + + %% show dewarped image + figure(1); + hold off; + imagesc(x_axis, y_axis, im_xy'); + xlim(x_lim); + ylim(y_lim); + colormap gray; + axis xy equal tight; + hold on; + set(gca,'Clim', image_clim); + for x = (round(x_lim(1)/grid_dx) : round(x_lim(end)/grid_dx)) * grid_dx + plot([x x], [y_axis(1) y_axis(end)], 'r-'); + end + for y = (round(y_lim(1)/grid_dy) : round(y_lim(end)/grid_dy)) * grid_dy + plot([x_axis(1) x_axis(end)], [y y], 'r-'); + end + + %% query user to save calibration or repeat + + sInput = input('Would you like to repeat the calibration? y to repeat: ', 's'); + if strcmpi(sInput, 'y') + continue; % continue without incrementing the counter + end + + %% save output + fprintf('Saving ... '); + fstruct = struct('source_image', im, ... + 'dewarped_image', im_xy, ... + 'plate_pos', plate_origin(:, calib_idx), ... + 'P', P2D, ... + 'i_axis', i_axis, 'j_axis', j_axis, ... + 'x_axis', x_axis, 'y_axis', y_axis, 'x_lim', x_lim, 'y_lim', y_lim, ... + 'grid_dx', grid_dx, 'grid_dy', grid_dy, ... + 'ij_markers', ij_markers, 'xy_markers', xy_markers, ... + 'camera_index', camera_index, ... + 'id', camera_id); + save(output_file, '-struct', 'fstruct'); + fprintf('done\n'); + calib_idx = calib_idx + 1; +end \ No newline at end of file diff --git a/calibration/find_markers.m b/calibration/find_markers.m new file mode 100644 index 0000000000000000000000000000000000000000..2ed367167736ad29c58151ec9829e82e945f8e17 --- /dev/null +++ b/calibration/find_markers.m @@ -0,0 +1,166 @@ +function [ij_markers, xy_markers] = find_markers(img, ij_initial_markers, dot_half_size) + % find_markers(img, ij_initial_markers) + % conduct a marker search after the user has identified six markers on + % the perimeter of a square, arranged like so: + % + % 5 x x x x 4 + % x x x x x x + % x x x x x x + % x x x x x x + % 6 x x x x x + % 1 2 x x x 3 + + %% stuff + Nx = size(img, 1); + Ny = size(img, 2); + thresh = 0.7; + grid_tol = 0.1; + n_iter = 3; + + %% find x&y position of six markers identified + xy_initial_markers= [0 1 nan nan 0 0; + 0 0 0 nan nan 1]; + e_x = ij_initial_markers(:, 3) - ij_initial_markers(:, 1); + e_y = ij_initial_markers(:, 5) - ij_initial_markers(:, 1); + e_x_norm = e_x / norm(e_x); + e_y_norm = e_y / norm(e_y); + dx_vec = norm(ij_initial_markers(:, 2) - ij_initial_markers(:,1)); + dy_vec = norm(ij_initial_markers(:, 6) - ij_initial_markers(:,1)); + + xy_initial_markers(1,3) = round( dot(ij_initial_markers(:,3) - ij_initial_markers(:,1), e_x_norm) / dx_vec ); + xy_initial_markers(2,5) = round( dot(ij_initial_markers(:,5) - ij_initial_markers(:,1), e_y_norm) / dy_vec ); + xy_initial_markers(1,4) = xy_initial_markers(1,3); + xy_initial_markers(2,4) = xy_initial_markers(2,5); + + %% produce initial pinhole model + % this maps ij ~ P * xy + ij_third = ij_initial_markers(:, 3); + ij_initial_markers= ij_initial_markers(:, [1 3 4 5]); + xy_initial_markers= xy_initial_markers(:, [1 3 4 5]); + P2D = dlt33([ij_initial_markers;ones(1,4)], [xy_initial_markers;ones(1,4)]); + for it = 1 : n_iter + %% dewarp with initial pinhole model + ij_corners = [0 0 Nx Nx; + 0 Ny Ny 0]; + ij_tilde = [ij_corners; 1 1 1 1]; + xy_tilde = P2D^-1 * ij_tilde; + lambda = xy_tilde(3,:); + xy_corners = [xy_tilde(1,:) ./ lambda; + xy_tilde(2,:) ./ lambda]; + x_lim = sort(xy_corners(1,:), 'ascend'); + y_lim = sort(xy_corners(2,:), 'ascend'); + x_lim = x_lim([1 end]); + y_lim = y_lim([1 end]); + + x_axis = linspace(x_lim(1), x_lim(2), Nx); + y_axis = linspace(y_lim(1), y_lim(2), Ny); + dx = x_axis(2) - x_axis(1); + dy = y_axis(2) - y_axis(1); + + [xmat, ymat] = ndgrid(x_axis, y_axis); + ij_tilde_dewarped = P2D * [xmat(:) ymat(:) ones(Nx*Ny,1)]'; + lambda = ij_tilde_dewarped(3,:); + i_dewarped = reshape(ij_tilde_dewarped(1,:) ./ lambda, [Nx Ny]); + j_dewarped = reshape(ij_tilde_dewarped(2,:) ./ lambda, [Nx Ny]); + + img_dewarped = interp2(1 : Nx, 1 : Ny, img', i_dewarped, j_dewarped); + + %% get dot img + [~,ij_third] = min(abs(x_axis)); + [~,ij_third(2)] = min(abs(y_axis)); + + dot_size = dot_half_size * 2 + 1; + dot_img_i = round(ij_third(1)) + (-dot_half_size(1) : dot_half_size(1)); + dot_img_j = round(ij_third(2)) + (-dot_half_size(2) : dot_half_size(2)); + dot_img = img_dewarped(dot_img_i, dot_img_j); + + %% convolve image and find indices of maxima + dot_img = dot_img - mean(dot_img(:)); + % subtract sliding average so DC component of x-correlation windows is + % zero + img_sliding_av = filter2(ones(size(dot_img)), img_dewarped) / numel(dot_img); + img_sub = img_dewarped - img_sliding_av; + % cross-correlate + convolved = filter2(dot_img, img_sub); + % normalise by image energy + image_energy = filter2(ones(size(dot_img)), img_sub.^2); + dot_energy = sum(dot_img(:).^2); + correl = convolved ./ (sqrt(image_energy) * sqrt(dot_energy)); + %correl = correl / correl(round(ij_center(1)), round(ij_center(2))); + + correl_left = inf([Nx Ny]); + correl_right = inf([Nx Ny]); + correl_above = inf([Nx Ny]); + correl_below = inf([Nx Ny]); + + correl_left(1:Nx-2, 2:Ny-1) = correl(2:Nx-1, 2:Ny-1); + correl_right(3:Nx, 2:Ny-1) = correl(2:Nx-1, 2:Ny-1); + correl_above(2:Nx-1, 1:Ny-2) = correl(2:Nx-1, 2:Ny-1); + correl_below(2:Nx-1, 3:Ny) = correl(2:Nx-1, 2:Ny-1); + + bw_markers = correl > correl_left ... + & correl > correl_right ... + & correl > correl_below ... + & correl > correl_above ... + & correl > thresh; + + %% find xy coordinates of markers + [i_markers,j_markers]= find(bw_markers); + xy_markers = [x_axis(i_markers(:)'); y_axis(j_markers(:)')]; + n_markers = size(xy_markers, 2); + + for mark = 1 : n_markers + x_range = abs(x_axis - xy_markers(1,mark)) < dx * dot_half_size(1); + y_range = abs(y_axis - xy_markers(2,mark)) < dy * dot_half_size(2); + sz = [sum(x_range), sum(y_range)]; + dot_img = img_dewarped(x_range,y_range); + dot_img_weighted = dot_img .* fspecial('gaussian', sz, norm(sz)/4); + dot_img_x = sum(dot_img_weighted, 2); + dot_img_y = sum(dot_img_weighted, 1); + xy_markers(1,mark)= sum(dot_img_x' .* x_axis(x_range)) / sum(dot_img_x); + xy_markers(2,mark)= sum(dot_img_y .* y_axis(y_range)) / sum(dot_img_y); + end + + %% only select markers which are approximately on regular grid + xy_frac = xy_markers - round(xy_markers); + marker_ok = abs(xy_frac(1,:)) < grid_tol & abs(xy_frac(2,:)) < grid_tol; + xy_markers = xy_markers(:, marker_ok); + xy_int = round(xy_markers); + + % sometimes, more than one identified marker can be attributed to the same grid + % point. eliminate these, based on which marker is closest + [~,ind] = unique(xy_int', 'rows'); + n_unique = length(ind); + xy_markers_uniq = zeros(2, n_unique); + for n = 1 : n_unique + ndist = sum(bsxfun(@minus, xy_markers,xy_int(:,ind(n))).^2, 1); + [~,inearest] = min(ndist); + xy_markers_uniq(:,n) = xy_markers(:, inearest); + end + xy_markers = xy_markers_uniq; + n_markers = n_unique; + + %% get i,j coord of markers + xy_tilde = [xy_markers; ones(1, n_markers)]; + ij_tilde = P2D * xy_tilde; + lambda = [1;1] * ij_tilde(3,:); + ij_markers = ij_tilde(1:2,:) ./ lambda; + + %% calculate a new calibration + xy_markers = round(xy_markers); + P2D = dlt33([ij_markers;ones(1,n_markers)], [xy_markers; ones(1,n_markers)]); + end + + %% display + figure(10); + hold off; + imagesc(1 : Nx, 1 : Ny, img'); + xlim([1 Nx]); + ylim([1 Ny]); + axis equal tight xy; + colormap gray; + hold on; + plot(ij_markers(1,:), ij_markers(2,:), 'r.'); +end + + diff --git a/calibration/simple_calibration.m b/calibration/simple_calibration.m new file mode 100644 index 0000000000000000000000000000000000000000..7e97a2e99b602e8d0fea19529036e2a62f8187db --- /dev/null +++ b/calibration/simple_calibration.m @@ -0,0 +1,223 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% bulk_marker_search.m +% find markers in images of calibration plates +% then perform a 2D calibration to dewarp the calibration plate image + +addpath([pwd '/../../mex/minmax/']); +addpath([pwd '/dlt/']); +addpath([pwd '/poly/']); +addpath([pwd '/../pinhole/']); +%% input parameters + +grid_dx = 20; % real units, mm +grid_dy = 20; +invert_image = 1; % invert image for black dots on white +blur_image = 0; % gaussian blur image +LoG_image = 0; % apply laplacian of Gaussian filter +normalise_image = 0; + + +output_dir = 'M:\mixer\calibration\demo\'; +input_file_list = { 'M:\mixer\calibration\demo\B00001.tif'}; +plate_origin = [0 0 0]'; +camera_index = 1; +camera_id = 1; +dot_half_window = [32 32]; +calib_idx_offset = 0; +origin_pos = [0 0]; + +image_clim = [0 255]; + +%% generate calibration for each image + +nCalibrations = length(input_file_list); + +calib_idx = 1; + +while calib_idx <= nCalibrations + %% update user of status + input_file = input_file_list{calib_idx}; + output_file = sprintf('%s/CAM%d-%02d.mat', output_dir, camera_index, calib_idx+calib_idx_offset); + fprintf('Image %02d of %02d:\n', calib_idx, nCalibrations); + + % check if calibration already exists + if exist(output_file, 'file') + sInput = input('Calibration already exists - overwrite? y to overwrite: ', 's'); + if ~strcmpi(sInput, 'y') + calib_idx = calib_idx + 1; + continue; + end + end + % check if image input exists + if ~exist(input_file, 'file') + fprintf(' - could not find image file\n'); + end + + %% load image, request position + im = imread(input_file, 2)'; + im = double(im); + im = fliplr(im); + Nx = size(im, 1); + Ny = size(im, 2); + + %% create filtered image for processing + im_filt = im; + if invert_image + im_filt = max(im(:))-im; + end + if blur_image + G = fspecial('gaussian', [5 5]); + im_filt = filter2(G, im_filt); + end + if LoG_image + scale = norm(dot_half_window)*0.2; + G = fspecial('log', 2*dot_half_window, scale); + im_filt = scale * conv2(G, im_filt); + end + if normalise_image + [minv, maxv] = minmaxfiltnd(single(im_filt), dot_half_window*2+1); + im_filt = (im_filt - minv) ./ max(16, maxv-minv); + end + + %% display image + figure(1); + hold off; + imagesc(im_filt'); + axis equal tight xy; + colormap gray; + %set(gca,'Clim', image_clim); + hold on; + colorbar; + + %% select region of interest + fprintf('Please select region of interest'); + [i_coord, j_coord] = ginput(2); + xlim(i_coord); + ylim(j_coord); + + %% get six marker positions + % center, first right, far right, far corner, far above, above + bDone = false; + while ~bDone + %% get marker position from user + fprintf('Please identify the six markers\n'); + fprintf('Press any key to abort and try again\n'); + + i_marker = nan(1,6); + j_marker = nan(1,6); + b_aborted = false; + h_plot = []; + for mark = 1 : 6 + %% get marker position from user input + [i_marker(mark) j_marker(mark), btn] = ginput(1); + if btn > 3 + b_aborted= true; + break; + end + %% get barycentre of marker, to be more accurate + dot_img_i = round(i_marker(mark)) + (-dot_half_window(1) : dot_half_window(1)); + dot_img_j = round(j_marker(mark)) + (-dot_half_window(2) : dot_half_window(2)); + [imat,jmat] = ndgrid(dot_img_i, dot_img_j); + dot_img = im_filt(dot_img_i, dot_img_j); + i_marker(mark) = sum(dot_img(:) .* imat(:)) / sum(dot_img(:)); + j_marker(mark) = sum(dot_img(:) .* jmat(:)) / sum(dot_img(:)); + + %% show user what he's plotting + delete(h_plot); + h_plot = plot(i_marker, j_marker, 'ro-'); + for it = 1 : mark + plot( i_marker(it)+[-1 -1 +1 +1 -1]*dot_half_window(1), ... + j_marker(it)+[-1 +1 +1 -1 -1]*dot_half_window(2), 'g-'); + end + end + if b_aborted + delete(h_plot); + continue; + end + bDone = true; + end + + %% find marker positions + [ij_markers, xy_markers]= find_markers(im_filt, [i_marker;j_marker], dot_half_window); + xy_markers(1,:) = xy_markers(1,:) * grid_dx + origin_pos(1); + xy_markers(2,:) = xy_markers(2,:) * grid_dy + origin_pos(2); + n_markers = size(xy_markers,2); + xy_tilde = [xy_markers; ones(1,n_markers)]; + + %% create calibration and dewarp + P2D = dlt33([ij_markers;ones(1,n_markers)], xy_tilde); + ij_tilde = P2D * xy_tilde; + lambda = [1; 1] * ij_tilde(3,:); + ij_fit = ij_tilde(1:2,:) ./ lambda; + + ij_error = ij_markers - ij_fit; + px_error = sqrt(ij_error(1,:).^2 + ij_error(2,:).^2); + rms_error = sqrt(mean(px_error.^2)); + + fprintf('RMS fit error: %0.2f px\n', rms_error); + + % work out x and y axes + ij_corners = [0 0 Nx Nx; + 0 Ny Ny 0]; + ij_tilde = [ij_corners; 1 1 1 1]; + xy_tilde = P2D^-1 * ij_tilde; + lambda = xy_tilde(3,:); + xy_corners = [xy_tilde(1,:) ./ lambda; + xy_tilde(2,:) ./ lambda]; + x_lim = sort(xy_corners(1,:), 'ascend'); + y_lim = sort(xy_corners(2,:), 'ascend'); + x_lim = x_lim([1 4]); + y_lim = y_lim([1 4]); + + x_axis = linspace(x_lim(1), x_lim(2), Nx); + y_axis = linspace(y_lim(1), y_lim(2), Ny); + i_axis = 1 : Nx; + j_axis = 1 : Ny; + % calculate dewarping + [xmat, ymat] = ndgrid(x_axis, y_axis); + ij_tilde_dewarped = P2D * [xmat(:) ymat(:) ones(Nx*Ny,1)]'; + lambda = ij_tilde_dewarped(3,:); + imat = reshape(ij_tilde_dewarped(1,:) ./ lambda, [Nx Ny]); + jmat = reshape(ij_tilde_dewarped(2,:) ./ lambda, [Nx Ny]); + im_xy = interp2(i_axis, j_axis, im', imat, jmat, 'linear'); + + %% show dewarped image + figure(1); + hold off; + imagesc(x_axis, y_axis, im_xy'); + xlim(x_lim); + ylim(y_lim); + colormap gray; + axis xy equal tight; + hold on; + set(gca,'Clim', image_clim); + for x = (round(x_lim(1)/grid_dx) : round(x_lim(end)/grid_dx)) * grid_dx + plot([x x], [y_axis(1) y_axis(end)], 'r-'); + end + for y = (round(y_lim(1)/grid_dy) : round(y_lim(end)/grid_dy)) * grid_dy + plot([x_axis(1) x_axis(end)], [y y], 'r-'); + end + + %% query user to save calibration or repeat + + sInput = input('Would you like to repeat the calibration? y to repeat: ', 's'); + if strcmpi(sInput, 'y') + continue; % continue without incrementing the counter + end + + %% save output + fprintf('Saving ... '); + fstruct = struct('source_image', im, ... + 'dewarped_image', im_xy, ... + 'plate_pos', plate_origin(:, calib_idx), ... + 'P', P2D, ... + 'i_axis', i_axis, 'j_axis', j_axis, ... + 'x_axis', x_axis, 'y_axis', y_axis, 'x_lim', x_lim, 'y_lim', y_lim, ... + 'grid_dx', grid_dx, 'grid_dy', grid_dy, ... + 'ij_markers', ij_markers, 'xy_markers', xy_markers, ... + 'camera_index', camera_index, ... + 'id', camera_id); + save(output_file, '-struct', 'fstruct'); + fprintf('done\n'); + calib_idx = calib_idx + 1; +end \ No newline at end of file diff --git a/image_preprocess.m b/image_preprocess.m index 42fe1a2650580a88f161f74469abb0c4c4ea0abe..e4336ccf3a5e694a3b66b0cb76ef71f47dc57a31 100644 --- a/image_preprocess.m +++ b/image_preprocess.m @@ -37,20 +37,46 @@ function im_filt = image_preprocess(im, filters) if isfield(filter, 'size'), sz = filter.size; end if isfield(filter, 'sigma'), sigma = filter.sigma; end G = fspecial('gaussian', sz, sigma); - im_filt = filter2(G, im_filt); + im_filt = filter2(G, im_filt); + elseif strcmp(filter.type, 'custom') + %% custom filter specified with fspecial + G = fspecial('gaussian', [3 3]); + if isfield(filter, 'kernel'), G = filter.kernel; end + im_filt = filter2(G, im_filt); elseif strcmp(filter.type, 'ssmin') %% subtract sliding minimum sz = [7 7]; if isfield(filter, 'size'), sz = filter.size; end sz = sz + mod(sz+1,2); - - minval = minmaxfiltnd(im_filt, sz); - im_filt = im_filt - minval; + minval = medfilt2(im_filt, [3 3]); % median filter first to avoid noise + % sliding minimum filter to get local minimum value + minval = minfiltnd(minval, sz); + % box filter to smooth background image + minval = convn(minval, ones(1, sz(1))/sz(1), 'same'); + minval = convn(minval, ones(sz(2), 1)/sz(2), 'same'); + % clipping operation to enforce positivity + im_filt = max(0, im_filt - minval); + elseif strcmp(filter.type, 'lmax') + %% local maximum + sz = [7 7]; + if isfield(filter, 'size'), sz = filter.size; end + sz = sz + mod(sz+1,2); + im_filt = maxfiltnd(im_filt, sz); elseif strcmp(filter.type, 'ssbg') %% subtract background - bg = zeros(size(im)); + bg = zeros(size(im)); if isfield(filter, 'bg'), bg = filter.bg; end - im_filt = max(0, im - bg); + im_filt = max(0, im - bg); + elseif strcmp(filter.type, 'levelize') + %% levelize + white = ones(size(im)); + if isfield(filter, 'white'), white = filter.white; end + im_filt = im_filt ./ white; + elseif strcmp(filter.type, 'median') + %% median filtering + sz = [5 5]; + if isfield(filter, 'size'), sz = filter.size; end + im_filt = medfilt2(im_filt, sz, 'symmetric'); elseif strcmp(filter.type, 'norm') %% normalisation filter sz = [7 7]; @@ -61,15 +87,60 @@ function im_filt = image_preprocess(im, filters) sz = sz + mod(sz+1,2); [minv, maxv] = minmaxfiltnd(im_filt, sz); - im_filt = (im_filt - minv) ./ max(maxv-minv, 1/max_gain); + im_filt = (im_filt - minv) ./ max(maxv-minv, 1/max_gain); + elseif strcmp(filter.type, 'norm2') + %% normalisation filter + sz = [7 7]; + max_gain = 1; + + if isfield(filter, 'size'), sz = filter.size; end + if isfield(filter, 'max_gain'), max_gain = filter.max_gain; end + sz = sz + mod(sz+1,2); + + [minv, maxv] = minmaxfiltnd(im_filt, sz); + % box filter local maximum + maxv = convn(maxv, ones(1, sz(1))/sz(1), 'same'); + maxv = convn(maxv, ones(sz(2), 1)/sz(2), 'same'); + % box filter local minimum + minv = convn(minv, ones(1, sz(1))/sz(1), 'same'); + minv = convn(minv, ones(sz(2), 1)/sz(2), 'same'); + im_filt = (im_filt - minv) ./ max(maxv-minv, 1/max_gain); + elseif strcmp(filter.type, 'mednorm') + %% normalisation filter with median filtering + sz = [7 7]; + max_gain = 1; + + if isfield(filter, 'size'), sz = filter.size; end + if isfield(filter, 'max_gain'), max_gain = filter.max_gain; end + sz = sz + mod(sz+1,2); + + % local maximum + maxv = minmaxfiltnd(im_filt, sz); + % box filter local maximum + maxv = convn(maxv, ones(1, sz(1))/sz(1), 'same'); + maxv = convn(maxv, ones(sz(2), 1)/sz(2), 'same'); + im_filt = max(0,im_filt) ./ max(maxv, 1/max_gain); elseif strcmp(filter.type, 'clip') - %% clip filter - im_med = median(im_filt(:)); - im_std = std(im_filt(:)); - n = 2; - if isfield(filter, 'n'), n = filter.n; end - thresh = im_med + n*im_std; - im_filt = min(im_filt, thresh); + %% clip filter, with automatic + if isfield(filter, 'threshold') + % manually specified threshold + threshold = filter.threshold; + im_filt = max(threshold(1), min(threshold(2), im_filt)); + else + % algorithmically specified threshold + im_med = median(im_filt(:)); + im_std = std(im_filt(:)); + n = 2; + if isfield(filter, 'n'), n = filter.n; end + thresh = im_med + n*im_std; + im_filt = min(im_filt, thresh); + end + elseif strcmp(filter.type, 'transpose') + im_filt = im_filt.'; + elseif strcmp(filter.type, 'invert') + offset = 0; + if isfield(filter, 'offset'), offset = filter.offset; end + im_filt = offset - im_filt; elseif strcmp(filter.type, 'null') %% null filter, do nothing im_filt = im_filt; diff --git a/job_create.m b/job_create.m index 199fccb6301079d25c2e263593be21954640e0a6..d8b11bd6fea492e4f210c35cd5c2e7709c0e3be0 100644 --- a/job_create.m +++ b/job_create.m @@ -10,7 +10,7 @@ addpath(genpath(fileparts(mfilename('fullpath')))); %% input parameters: file paths % where to save jobs job_idx = 1; -job_dir = 'J:\FEE\EFMRL\John\TR-IM7\jobs\'; +job_dir = 'F:\mixer\0609\run01\'; job_pat = '%s/job%03d.mat'; % timeseries PIV flag @@ -18,16 +18,16 @@ job_pat = '%s/job%03d.mat'; % in a timeseries b_timeseries = true; % path of images to process -src_dir = 'J:\FEE\EFMRL\John\TR-IM7'; -src_pat = '%s/B%05d.im7'; % source image to process -src_range = 1:3; % range of source images to process +src_dir = 'M:\mixer\151020\dt1.00\'; +src_pat = '%s/B%05d.tif'; % source image to process +src_range = 1:10; % range of source images to process % background & mask -bg_dir = src_dir; % background image directory -bg_pat = '%s/minmax.mat'; % background image (ignore if not using) +bg_dir = ''; % background image directory +bg_pat = ''; % background image (ignore if not using) msk_dir = [src_dir '/MASK/']; % mask image directory msk_pat = '%s/B0002.im7'; % pattern for mask image input (ignore if not using) % where to save result -vec_dir = [src_dir '/C12']; % directory to save vector fields to +vec_dir = [src_dir '/C1']; % directory to save vector fields to vec_pat = '%s/B%05d.mat'; % output vector field file pattern %% input parameters: processing @@ -37,7 +37,7 @@ b_overwrite = true; % overwrite existing vector fields? b_execute_now = true; % start processing now? % PIV correlation settings -i_frames = {[1 1],[2 2], [3 3]}; % indices of frame pairs to cross-correlate +i_frames = {[1 2]}; % indices of frame pairs to cross-correlate n_passes = 3; % number of passes n_peaks = 3; % number of correlation peaks to detect wsize = [64 64; 32 32; 32 32];% window size @@ -122,6 +122,7 @@ for n = 1 : n_src end end fprintf('Loading sample %s\n', src_name); + % load image imx = readimx(src_name); im_ary = cellfun(@(f) double(f.Components{1}.Planes{1}), imx.Frames, 'UniformOutput', false); diff --git a/job_create_2d.m b/job_create_2d.m new file mode 100644 index 0000000000000000000000000000000000000000..abd6474e3de42f2d24e54c525fda80ff722da877 --- /dev/null +++ b/job_create_2d.m @@ -0,0 +1,270 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% create job file for PIV processing +% +% Author: John Lawson +% j.m.lawson@soton.ac.uk +% Created: 11/09/2019 +% +addpath(genpath(fileparts(mfilename('fullpath')))); +addpath([pwd '/../misc']); + +%% input parameters: file paths +% where to save jobs +job_idx = 24; +job_dir = 'sampledata'; +job_pat = '%s/job%03d.mat'; + +% timeseries PIV flag +% in timeseries PIV, correlations are conducted between subsequent images +% in a timeseries +b_timeseries = true; +% path of images to process +dset = sprintf('run%02d', job_idx); +src_dir = ['sampledata/']; +src_pat = '%s/NACA0020_%05d.jpg'; % source image to process +src_range = 0:1; % range of source images to process +% background & mask +bg_dir = src_dir; % background image directory +bg_pat = '%s/Bmin.tif'; % background image (ignore if not using) +msk_dir = [src_dir '/MASK/']; % mask image directory +msk_pat = '%s/B0002.im7'; % pattern for mask image input (ignore if not using) +% where to save result +vec_dir = ['sampledata/']; % directory to save vector fields to +vec_pat = '%s/B%05d.mat'; % output vector field file pattern +% calibration +b_transpose = true; +b_apply_calib = false; +cal_dir = 'M:\mixer\calibration\20-12-09_02\'; +cal_pat = '%s/CAM1-01.mat'; + +%% input parameters: processing +% processing flags +b_use_mask = false; % use mask file? +b_overwrite = true; % overwrite existing vector fields? +b_execute_now = true; % start processing now? + +% PIV correlation settings +i_frames = {[1 1]}; % indices of frame pairs to cross-correlate +n_passes = 3; % number of passes +n_peaks = 3; % number of correlation peaks to detect +wsize = [64 64; 32 32; 32 32];% window size +wtype = {'gaussian','gaussian','gaussian','gaussian'}; % window weighting function type +overlap = [50 50 50]; % window overlap % +peak_finder = 'gauss3'; % peak finder: + % gauss 3 (3 point fit) + % gauss 4 (4 parameter lsq gaussian fit) + % gauss 5 (5 parameter lsq gaussian fit) + % gauss 6 (6 parameter lsq gaussian fit) + +%% input parameters: plotting features +plt_opts = struct(); +plt_opts.c_lim = [0 1]*256; +plt_opts.font_size = 24; % figure font size +plt_opts.b_plot_src = true & b_execute_now; % plot source images? +plt_opts.b_plot_u = false & b_execute_now; % plot u component field? +plt_opts.b_plot_v = false & b_execute_now; % plot v component field? +plt_opts.b_plot_cv = false & b_execute_now; % plot correlation value? + +%% input parameters: image preprocessing toolchain +% add filters in order in which they are to be applied +% see image_preprocess for more details and options of filters +min_filt_size = [7 7];% [11 11]; % sliding bg filter size +gauss_filt_size = [3 3]; % gaussian filter size +norm_filt_size = [9 9]; % normalisation filter size +log_filt_size = [9 9]; % Laplacian-of-gaussian filter size +log_sigma = 1.2; % Laplacian-of-Gaussian filter width + +filters = {struct('type', 'null')}; + +% % subtract background image: will require background image to be supplied +%filters{end+1} = struct('type', 'ssbg'); +% % subtract sliding minimum +%filters{end+1} = struct('type', 'ssmin', 'size', min_filt_size); +% % max-clip bright particles at threshold = median(im) + n*sigma(im) +filters{end+1} = struct('type', 'clip', 'n', 2); +% % 2D Laplacian of Gaussian filter +%G_filt = -fspecial('log', log_filt_size, log_sigma); +%filters{end+1} = struct('type', 'custom', 'kernel', G_filt); +% 2D gaussian filter +filters{end+1} = struct('type', 'gaussian', 'size', gauss_filt_size, 'sigma', 0.67); +% % min-max normalisation filter +%filters{end+1} = struct('type', 'norm2', 'size', norm_filt_size, 'max_gain', 1/32); +%filters{end+1} = struct('type', 'mednorm', 'size', norm_filt_size, 'max_gain', 1); +% transpose image +if b_transpose + filters{end+1} = struct('type', 'transpose'); +end + +%% input parameters: vector postprocesing +% load calibration +operations = {struct('type', 'null')}; + +if b_apply_calib + cal_file = sprintf(cal_pat,cal_dir); + calib = matfile(cal_file,'writable',false); + P2D = calib.P; + + operations{end+1} = struct('type', 'scale2d', ... + 'P', P2D); +end + +%% check job directory +job_completed_dir = [job_dir '/completed/']; +job_busy_dir = [job_dir '/busy/']; +job_file = sprintf(job_pat, job_dir, job_idx); + +fprintf('------------------------------------------------------------------\n'); +fprintf('Creating job %s\n', job_file); +% check directory exists +if ~exist(job_dir, 'dir') + mkdir(job_dir); + %error('job directory %s does not exist'); +end +% check complete and busy directories are there +if ~exist(job_completed_dir,'dir') + fprintf('making completed jobs directory ...\n'); + mkdir(job_completed_dir); +end +if ~exist(job_busy_dir,'dir') + fprintf('making busy jobs directory ...\n'); + mkdir(job_busy_dir); +end +% check if existing job file already exists +if exist(job_file, 'file') + s_override = input('job already exists, are you sure you want to overwrite? y/N ', 's'); + if ~strcmp(s_override, 'y') + error('will not overwrite job'); + end +end +% make output directory +if ~exist(vec_dir, 'dir') + fprintf('making output directory ...\n'); + mkdir(vec_dir); +end + +%% load sample and check all files in range exist +n_src = length(src_range); +fprintf('------------------------------------------------------------------\n'); +fprintf('Checking %d files in %s ...\n', n_src, src_dir); +for n = 1 : n_src + src_name = sprintf(src_pat, src_dir, src_range(n)); + if ~exist(src_name, 'file') + error('source %s does not exist\n', src_name); + end +end +fprintf('Loading sample %s\n', src_name); +% load image +im_ary = readimg(src_name); +% image properties +n_frames = length(im_ary); +im_sz = size(im_ary{1}); +assert(all(all(cell2mat(i_frames)>=1 & cell2mat(i_frames) <= n_frames)), 'PIV correlations must be specified between valid frame indices'); + +% report +fprintf('frames: %d\n', n_frames); +fprintf('resolution: %d x %d\n', im_sz); + +%% check mask +if b_use_mask + msk_name = sprintf(msk_pat, msk_dir); + fprintf('Checking mask %s ...\n', msk_name); + im_mask_ary = readimg(msk_name); + assert(length(im_mask_ary) == n_frames, 'mask image does not contain the right number of frames'); + assert(all(size(im_mask_ary{1}) == im_sz), 'mask image is not the same size as source'); +end + +%% load background image +% flag to indicate we need to load +b_use_bg = any(cellfun(@(f)strcmp(f.type,'ssbg'),filters)); + +if b_use_bg + bg_name = sprintf(bg_pat, bg_dir); + fprintf('Checking background %s\n', bg_name); + assert(~~exist(bg_name,'file'), 'background image is required, but file is missing'); + im_bg_ary = readimg(bg_name); + assert(length(im_bg_ary) == n_frames, 'background image does not contain the right number of frames'); + assert(all(size(im_bg_ary{1}) == im_sz), 'background image is not the same size as source'); +end + +%% define PIV image grid +% zero indexed +% TODO: make this match the internal scale/offset of im7 image files +if b_transpose + Nx = im_sz(2); + Ny = im_sz(1); +else + Nx = im_sz(1); + Ny = im_sz(2); +end +im_x = 0 : Nx-1; +im_y = 0 : Ny-1; + +%% define PIV options structure +% create piv options array +% note clever trick with i_frames cell array, which will generate array of +% options +piv_opts = struct('im_x', im_x, ... + 'im_y', im_y, ... + 'dt', 1, ... + 'i_frames', i_frames, ... + 'n_passes', n_passes, ... + 'n_peaks', n_peaks, ... + 'wsize', wsize, ... + 'overlap', overlap, ... + 'peak_finder', peak_finder, ... + 'median_filter', true, ... + 'epsilon_0', 0.2, ... % typical PIV error, px + 'epsilon_thresh', 2, ... % replace if vector exceeds n*RMS of neighbours + 'mask_thresh', 0.25); % maximum acceptable fraction of masked pixels in IW +% add window type field +n_correl = length(piv_opts); +for n = 1 : n_correl + piv_opts(n).wtype = wtype; +end + +%% bundle into structure +job = struct(); +% job details +job.job_idx = job_idx; +job.job_dir = job_dir; +job.job_pat = job_pat; +% timeseries flag +job.b_timeseries= b_timeseries; +% source images +job.src_dir = src_dir; +job.src_pat = src_pat; +job.src_range = src_range; +% background and mask +job.b_use_bg = b_use_bg; +job.bg_dir = bg_dir; +job.bg_pat = bg_pat; +job.b_use_mask = b_use_mask; +job.msk_dir = msk_dir; +job.msk_pat = msk_pat; +% output vector fields +job.b_overwrite=b_overwrite; +job.vec_dir = vec_dir; +job.vec_pat = vec_pat; + +% filtering +job.filters = filters; + +% PIV processing options +job.n_correl = n_correl; +job.piv_opts = piv_opts; + +% postprocessing operations +job.operations = operations; + +% plotting options +job.plt_opts = plt_opts; + +%% save job to file +fprintf('saving job ... \n'); +save(job_file, '-struct', 'job'); + +%% execute +if b_execute_now + job_execute(job_file); + delete(job_file); +end \ No newline at end of file diff --git a/job_create_postproc.m b/job_create_postproc.m new file mode 100644 index 0000000000000000000000000000000000000000..3dd5cbe074f964be3025b0d7d056be89d6a13b40 --- /dev/null +++ b/job_create_postproc.m @@ -0,0 +1,108 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% create job file for PIV processing +% +% Author: John Lawson +% j.m.lawson@soton.ac.uk +% Created: 11/09/2019 +% +addpath(genpath(fileparts(mfilename('fullpath')))); + +%% input parameters: file paths +% where to save jobs +job_idx = 1; +job_dir = 'F:\mixer\091020\jobs\'; +job_pat = '%s/pproc%03d.mat'; + +% path of vector fields to postprocess +src_dir = ['F:\mixer\091020\' sprintf('run%02d', job_idx)]; +src_pat = '%s/C1/B%05d.mat'; % source image to process +src_range = 1:1000; % range of source images to process +% where to save result +dst_dir = src_dir; % directory to save vector fields to +dst_pat = '%s/V%05d.mat'; % output vector field file pattern +% 2D calibration +cal_dir = 'F:\mixer\061020\calibration\'; +cal_pat = '%s/CAM1-01.mat'; +piv_dt_range = [20 20 20 20 15 7.5 30 20 15]*1E-3; + +%% input parameters: vector postprocesing +% load calibration +cal_file = sprintf(cal_pat,cal_dir); +calib = load(cal_file); +P2D = calib.P; + +operations = {struct('type', 'null')}; +operations{end+1} = struct('type', 'scaledt', ... + 'dt', piv_dt_range(job_idx)); +operations{end+1} = struct('type', 'scale2d', ... + 'P', P2D); + +%% check job directory +job_completed_dir = [job_dir '/completed/']; +job_busy_dir = [job_dir '/busy/']; +job_file = sprintf(job_pat, job_dir, job_idx); + +fprintf('------------------------------------------------------------------\n'); +fprintf('Creating job %s\n', job_file); +% check directory exists +if ~exist(job_dir, 'dir') + error('job directory %s does not exist'); +end +% check complete and busy directories are there +if ~exist(job_completed_dir,'dir') + fprintf('making completed jobs directory ...\n'); + mkdir(job_completed_dir); +end +if ~exist(job_busy_dir,'dir') + fprintf('making busy jobs directory ...\n'); + mkdir(job_busy_dir); +end +% check if existing job file already exists +if exist(job_file, 'file') + s_override = input('job already exists, are you sure you want to overwrite? y/N ', 's'); + if ~strcmp(s_override, 'y') + error('will not overwrite job'); + end +end +% make output directory +if ~exist(dst_dir, 'dir') + fprintf('making output directory ...\n'); + mkdir(dst_dir); +end + +%% load sample and check all files in range exist +n_src = length(src_range); +fprintf('------------------------------------------------------------------\n'); +fprintf('Checking %d files in %s ...\n', n_src, src_dir); +for n = 1 : n_src + src_name = sprintf(src_pat, src_dir, src_range(n)); + if ~exist(src_name, 'file') + error('source %s does not exist\n', src_name); + end +end + +%% bundle into structure +job = struct(); +% job details +job.job_idx = job_idx; +job.job_dir = job_dir; +job.job_pat = job_pat; +% source images +job.src_dir = src_dir; +job.src_pat = src_pat; +job.src_range = src_range; +% output vector fields +job.b_overwrite=b_overwrite; +job.dst_dir = dst_dir; +job.dst_pat = dst_pat; +% postprocessing operations +job.operations = operations; + +%% save job to file +fprintf('saving job ... \n'); +save(job_file, '-struct', 'job'); + +%% execute +if b_execute_now + job_postprocess(job_file); +end \ No newline at end of file diff --git a/job_execute.m b/job_execute.m index 6d1d3d6f2718fc7f27f2b19d6be11e812561b8af..ffef2fe5fea6379e22353047a0c1c13e5d0c3ca0 100644 --- a/job_execute.m +++ b/job_execute.m @@ -30,6 +30,11 @@ function job_execute(job_file) piv_opts = job.piv_opts; % plotting options plt_opts = job.plt_opts; + % postprocessing + operations = {}; + if isfield(job, 'operations') + operations = job.operations; + end %% report fprintf('--------------------------------------------------------------\n'); @@ -39,8 +44,10 @@ function job_execute(job_file) %% load prototype image % load first image src_name = sprintf(src_pat, src_dir, src_range(1)); - imx = readimx(src_name); - im_ary = cellfun(@(f) double(f.Components{1}.Planes{1}), imx.Frames, 'UniformOutput', false); + im_ary = readimg(src_name); + % apply test preprocessing to first image + % this handles things like changing image size + im_ary{1} = image_preprocess(im_ary{1}, filters); % extract properties n_frames = length(im_ary); im_sz = size(im_ary{1}); @@ -49,8 +56,9 @@ function job_execute(job_file) if b_use_bg bg_name = sprintf(bg_pat, bg_dir); fprintf('Load background %s\n', bg_name); - s_bg = importdata(bg_name); - im_bg_ary = s_bg.min; + %s_bg = importdata(bg_name); + %im_bg_ary = s_bg.min; + im_bg_ary = readimg(bg_name); else im_bg_ary = {zeros(im_sz)}; im_bg_ary(1:n_frames) = im_bg_ary; @@ -60,8 +68,7 @@ function job_execute(job_file) if b_use_mask msk_name = sprintf(msk_pat, msk_dir); fprintf('Load mask %s\n', msk_name); - imx_mask = readimx(msk_name); - im_mask_ary = cellfun(@(f) f.Components{2}.Planes{1} == 0, imx_mask.Frames, 'UniformOutput', false); + im_mask_ary = readimg(msk_name); else im_mask_ary = {false(im_sz)}; im_mask_ary(1:n_frames) = im_mask_ary; @@ -85,19 +92,19 @@ function job_execute(job_file) continue; end - % load image - imx = readimx(src_name); - im_ary = cellfun(@(f) double(f.Components{1}.Planes{1}), imx.Frames, 'UniformOutput', false); - - % read attributes - atts = readAttributes(imx); - if isKey(atts, 'Reference time dt 1') - s = atts('Reference time dt 1'); - piv_dt = 1E-6 * s.value; - else - warning('dt field is missing from IM7 file, dt = 1'); - piv_dt = 1; - end + % load image and attributes + [im_ary, atts] = readimg(src_name); + + %% read attributes + if isKey(atts, 'Reference time dt 1') + s = atts('Reference time dt 1'); + piv_dt = 1E-6 * s.value; + elseif isKey(atts, 'DeltaT') + piv_dt = atts('DeltaT'); + else + warning('dt field is missing from file, dt = 1'); + piv_dt = 1; + end t_load = toc(t_samp); %% preprocess images @@ -155,34 +162,57 @@ function job_execute(job_file) im_A = im_pairs{1,c}; im_B = im_pairs{2,c}; + + + %% cross-correlate + fprintf(' correlation %d of %d\n', c, n_correl); + piv_result{c} = PIV_2D_wdef(im_A, im_B, ... + im_correl_mask{c}, ... + piv_opts(c)); + %% plot source, if requested if plt_opts.b_plot_src + %% result at final pass + V = piv_result{c}(end); + + %% source image figure(10); clf; - imshowpair(im_A', im_B'); + ax_src = gca; + h_pair = imshowpair(im_A', im_B'); + h_pair.XData = V.win_x; + h_pair.YData = V.win_y; axis equal tight ij; + + %% overlay vector field + V.peak_mag(V.nan_mask)= nan; + hold on; + [xm,ym] = ndgrid(V.win_x, V.win_y); + ir = 1 : 2 : V.n_windows(1); + jr = 1 : 2 : V.n_windows(2); + quiversc(xm(ir,jr),ym(ir,jr),V.ux(ir,jr),V.uy(ir,jr),10,'k'); + drawnow; end - - %% cross-correlate - fprintf(' correlation %d of %d\n', c, n_correl); - piv_result{c} = PIV_2D_wdef(im_A, im_B, ... - im_correl_mask{c}, ... - piv_opts(c)); end t_proc = toc(t_proc); - - %% save result - t_save = tic; - fprintf(' save to %s\n', vec_name); + + %% postprocess + % store result in structure for saving now + % only apply postprocessing to final pass + fprintf(' postprocessing...\n'); fs = struct(); - % only save final pass for c = 1 : n_correl vname = sprintf('C%d',c); - VC = piv_result{c}(end); + VC = scaledt(piv_result{c}(end), piv_dt); + VC = vector_postprocess(VC, operations); VC.attributes= atts; fs.(vname) = VC; end + + %% save result + t_save = tic; + fprintf(' save to %s\n', vec_name); if exist(vec_name, 'file') warning('%s already exists, overwriting result', vec_name); end @@ -191,19 +221,24 @@ function job_execute(job_file) %% report t_samp = toc(t_samp); - fprintf(' took %0.1f s [%0.1f load, %0.1f preproc, %0.1f proc, %0.1f save]\n', t_samp, t_load, t_preproc, t_proc, t_save); + fprintf(' took %0.2f s [%0.2f load, %0.2f preproc, %0.2f proc, %0.2f save]\n', t_samp, t_load, t_preproc, t_proc, t_save); fprintf(' %2d h %2d m %2d s elapsed so far\n', hms(toc(t_begin))); + %% figure settings + offset = 10; + + %% plot vector field pass = length(piv_result{1}); if plt_opts.b_plot_u - figure(1); + figure(1+offset); clf; ax = []; for c = 1 : n_correl V = piv_result{c}(pass); + V.ux(V.nan_mask)= nan; subplot(1,n_correl,c); - ax(c) = gca; + ax(end+1) = gca; imagesc(V.win_x, V.win_y, V.ux') axis equal tight ij; colormap(redblue); @@ -217,18 +252,19 @@ function job_execute(job_file) %% plot v component field if plt_opts.b_plot_v - figure(2); + figure(2+offset); clf; ax = []; for c = 1 : n_correl V = piv_result{c}(pass); + V.uy(V.nan_mask)= nan; subplot(1,n_correl,c); - ax(c) = gca; + ax(end+1) = gca; imagesc(V.win_x, V.win_y, V.uy') axis equal tight ij; colormap(redblue); colorbar; - set(gca,'clim',[-4 4]); + set(gca,'clim',[-16 16]); title(sprintf('v component, camera %d', c)); end linkaxes(ax,'xy'); @@ -237,13 +273,14 @@ function job_execute(job_file) %% plot correlation value if plt_opts.b_plot_cv - figure(4); + figure(4+offset); clf; ax = []; for c = 1 : n_correl V = piv_result{c}(pass); subplot(1,n_correl,c); - ax(c) = gca; + ax(end+1) = gca; + V.peak_mag(V.nan_mask)= nan; imagesc(V.win_x, V.win_y, V.peak_mag') axis equal tight ij; hold on; @@ -258,6 +295,7 @@ function job_execute(job_file) end linkaxes(ax,'xy'); drawnow; + fprintf('mean correlation value = %0.2f\n', nanmean(V.peak_mag(:))); end end end \ No newline at end of file diff --git a/job_postprocess.m b/job_postprocess.m new file mode 100644 index 0000000000000000000000000000000000000000..3fdd3555f38035a946aae5e46b4c15d023e40fbf --- /dev/null +++ b/job_postprocess.m @@ -0,0 +1,65 @@ +function job_postprocess(job_file) + % load a PIV processing job and execute it + + %% load options from job file + job = importdata(job_file); + % source images + src_dir = job.src_dir; + src_pat = job.src_pat; + src_range = job.src_range; + n_src = length(src_range); + % output vector fields + b_overwrite = job.b_overwrite; + dst_dir = job.dst_dir; + dst_pat = job.dst_pat; + % postprocessing operations to perform + operations = job.operations; + + %% report + fprintf('--------------------------------------------------------------\n'); + fprintf('PROCESSING START %s\n', datestr(now)); + t_begin = tic; + + + %% loop over files to be processed + for n = 1 : n_src + %% load + t_samp = tic; + % check source exists + src_name = sprintf(src_pat, src_dir, src_range(n)); + fprintf('[%3d/%3d] %s \n', n, n_src, src_name); + if ~exist(src_name, 'file') + warning('source %s does not exist, skipping\n', src_name); + continue; + end + % check output does not exist + dst_name = sprintf(dst_pat, dst_dir, src_range(n)); + if exist(dst_name, 'file') && ~b_overwrite + warning('output %s already exists, skipping\n', dst_name); + continue; + end + % load vector field from file + t_load = tic; + V = load(src_name); + t_load = toc(t_load); + + %% apply postprocessing chain to vector field + t_proc = tic; + W = vector_postprocess(V.C1, operations); + t_proc = toc(t_proc); + + %% save result + t_save = tic; + fprintf(' save to %s\n', dst_name); + if exist(dst_name, 'file') + warning('%s already exists, overwriting result', dst_name); + end + save(dst_name, '-struct', 'W'); + t_save = toc(t_save); + + %% report + t_samp = toc(t_samp); + fprintf(' took %0.1f s [%0.1f load, %0.1f proc, %0.1f save]\n', t_samp, t_load, t_proc, t_save); + fprintf(' %2d h %2d m %2d s elapsed so far\n', hms(toc(t_begin))); + end +end \ No newline at end of file diff --git a/misc/gradient_o2_2d.m b/misc/gradient_o2_2d.m new file mode 100644 index 0000000000000000000000000000000000000000..b1f07b27ca369291ef4f96df9c17f111aa482f51 --- /dev/null +++ b/misc/gradient_o2_2d.m @@ -0,0 +1,33 @@ +function [fx, fy] = gradient_o4_2d(f, dx, dy) + % calculate second order accurate approximation to gradient of f(x,y) + % using the formula + % df/dx = (u(i+1,j,k) - u(i-1,j,k))/(2dx) + + %% fill in missing arguments + if nargin < 2 + dx = 1; + end + if nargin < 3 + dy = dx; + end + + %% generate kernels + kernel_x = reshape([1 0 -1] / (2*dx), [3 1]); + kernel_y = reshape([1 0 -1] / (2*dy), [1 3]); + + %% convolve over given dimension + fx = convn(f, kernel_x, 'same'); + fy = convn(f, kernel_y, 'same'); + + %% mask invalid regions with nan + if size(f,1) >= 3 + fx([1 end],:) = nan; + else + fx = nan(size(f)); + end + if size(f,2) >= 3 + fy(:,[1 end]) = nan; + else + fy = nan(size(f)); + end +end \ No newline at end of file diff --git a/misc/gradient_o4_2d.m b/misc/gradient_o4_2d.m new file mode 100644 index 0000000000000000000000000000000000000000..9f3844765c6ed9908aea848bb855dfa6110621c8 --- /dev/null +++ b/misc/gradient_o4_2d.m @@ -0,0 +1,34 @@ +function [fx, fy] = gradient_o4_2d(f, dx, dy) + % calculate fourth order accurate approximation to gradient of f(x,y) + % using the formula + % df/dx = (-u(i+2,j,k) + 8u(i+1,j,k) - 8u(i-1,j,k) + u(i-2,j,k))/(12dx) + % df/dy = (-u(i,j+2,k) + 8u(i,j+1,k) - 8u(i,j-1,k) + u(i,j-2,k))/(12dy) + + %% fill in missing arguments + if nargin < 2 + dx = 1; + end + if nargin < 3 + dy = dx; + end + + %% generate kernels + kernel_x = reshape([-1 8 0 -8 1] / (12*dx), [5 1 1]); + kernel_y = reshape([-1 8 0 -8 1] / (12*dy), [1 5 1]); + + %% convolve over given dimension + fx = convn(f, kernel_x, 'same'); + fy = convn(f, kernel_y, 'same'); + + %% mask invalid regions with nan + if size(f,1) > 5 + fx([1:2 end-1 end],:)= nan; + else + fx = nan(size(f)); + end + if size(f,2) > 5 + fy(:,[1:2 end-1 end])= nan; + else + fy = nan(size(f)); + end +end \ No newline at end of file diff --git a/misc/readimg.m b/misc/readimg.m new file mode 100644 index 0000000000000000000000000000000000000000..093ab159237d4639f4eaac11fd974f5786ab5b4a --- /dev/null +++ b/misc/readimg.m @@ -0,0 +1,56 @@ +function [im_ary, atts] = readimg(file_name) + [~,~,ext] = fileparts(file_name); + if strcmpi(ext, '.imx') + %% load imx image array + imx = readimx(src_name); + im_ary = cellfun(@(f) single(f.Components{1}.Planes{1}), imx.Frames, 'UniformOutput', false); + atts = readAttributes(imx); + elseif strcmpi(ext, '.tif') || strcmpi(ext, '.tiff') + %% load TIF image array + info = imfinfo(file_name); + n_pages = length(info); + im_ary = cell(1, n_pages); + for n = 1 : n_pages + im_ary{n} = single(imread(file_name, n)); + end + + %% read optional attributes from TIFF XMP tag + % using custom headers from gige data logger + t = Tiff(file_name, 'r'); + xmlstr = '<metadata></metadata>'; + try + xmlstr = sprintf('<metadata>\n%s\n</metadata>', t.getTag('XMP')); + end + dom = xmlstr2struct(xmlstr); + metadata = dom.metadata; + atts = containers.Map; + % parse fields, if present + if isfield(metadata, 'AcqTime') + atts('AcqTime') = metadata.AcqTime.Text; + end + if isfield(metadata, 'DeltaT') + % hard coded to use microseconds as units for now + % unit information is provided in + units_to_seconds= 1E-6; + atts('DeltaT') = str2double(metadata.DeltaT.Text) * units_to_seconds; + end + if isfield(metadata, 'Exposure') + units_to_seconds= 1E-6; + atts('Exposure')= str2double(metadata.Exposure.Text) * units_to_seconds; + end + if isfield(metadata, 'SampleIndex') + atts('SampleIndex') = str2double(metadata.SampleIndex.Text); + end + elseif strcmpi(ext, '.jpg') + %% load single JPG image + img = imread(file_name); + % convert to grayscale if necessary + img = sqrt(sum(img.^2, 3)); + im_ary = {img}; + atts = containers.Map; + atts('SampleIndex')= 1; + atts('DeltaT') = 1; + else + error('unrecognised file format'); + end +end \ No newline at end of file diff --git a/misc/scale2d.m b/misc/scale2d.m new file mode 100644 index 0000000000000000000000000000000000000000..70b40b79d782d22669bd788a086ff52c1abf02b8 --- /dev/null +++ b/misc/scale2d.m @@ -0,0 +1,64 @@ +function new = scale2d(old, calib, b_keep) + % W = scale2d(old, calib) + % rescale a 2D vector field of pixel displacement by a specified calibration + % + % Input: + % old vector field structure with image space coordinates + % calib calibration structure + % b_keep flag to keep pixel displacement information + % + % Output: + % new vector field with components rescaled to object space + % + % calibration structure contains: + % model char (optional) string describing type of calibration model + % P 3x3 linear transformation matrix + % + % currently, only a linear transformation is implemented + % see p2d_im2obj and p2d_obj2im + % + % linear transform is defined + % lambda * [x;y;1] = P*[X;Y;1] + % where + % (x,y) coordinates in image space + % (X,Y) coordinates in object space + % lambda scale factor + + %% extract velocity in image coordinates + im_u = old.ux; + im_v = old.uy; + + %% apply transformation to position + [im_x, im_y]= ndgrid(old.win_x, old.win_y); + n_vec = numel(im_x); + n_windows = size(im_x); + im_pos = [im_x(:) im_y(:)]'; + gl_pos = p2d_map_im2obj(calib.P, im_pos); + X = reshape(gl_pos(1,:), n_windows); + Y = reshape(gl_pos(2,:), n_windows); + + %% apply transformation to displacement + im_aug = [im_pos; ones(1, n_vec)]; + UV_aug = zeros(3, n_vec); + P = calib.P; + lambda = P(3,:)*[gl_pos; ones(1, n_vec)]; + for k = 1 : n_vec + P_aug = [P(:,1:2), -im_aug(:,k)]; + UV_aug(:,k) = lambda(k) * (P_aug \ [im_u(k); im_v(k); 0]); + end + U = reshape(UV_aug(1,:), n_windows); + V = reshape(UV_aug(2,:), n_windows); + + %% update fields + % discard old fields + new = old; + if nargin < 3 || ~b_keep + new = rmfield(new, {'ux', 'uy', 'win_x', 'win_y'}); + end + % add new fields + new.X = X; + new.Y = Y; + new.U = U; + new.V = V; + new.calib = calib; +end \ No newline at end of file diff --git a/misc/scaledt.m b/misc/scaledt.m new file mode 100644 index 0000000000000000000000000000000000000000..3a9c484e727e3067588877577dadc4b242a465f5 --- /dev/null +++ b/misc/scaledt.m @@ -0,0 +1,28 @@ +function new = scaledt(old, new_dt) + % W = scaledt(V, scale) + % rescale PIV delta-t by new value + % U -> U * dt / new_dt + % dt -> new_dt + + %% define new scale factor + new = old; + scale = old.dt / new_dt; + new.dt = new_dt; + + %% apply scale to velocity components if present + if isfield(old, 'ux') + new.ux = old.ux * scale; + end + if isfield(old, 'uy') + new.uy = old.uy * scale; + end + if isfield(old, 'U') + new.U = old.U * scale; + end + if isfield(old, 'V') + new.V = old.V * scale; + end + if isfield(old, 'W') + new.W = old.W * scale; + end +end \ No newline at end of file diff --git a/misc/test_scale2d.m b/misc/test_scale2d.m new file mode 100644 index 0000000000000000000000000000000000000000..85ac018c362bac52d0a16a681483a7fc48b3e968 --- /dev/null +++ b/misc/test_scale2d.m @@ -0,0 +1,42 @@ + +clear; +load('F:\mixer\231020\calibration\CAM1-01.mat'); + +%% create a random velocity field +win_x = 16 : 16 : 6600; +win_y = 16 : 16 : 4400; +[im_x,im_y] = ndgrid(win_x, win_y); +im_pos = [im_x(:) im_y(:)]'; +obj_pos = p2d_map_im2obj(P, im_pos); +n_windows = size(im_x); + +% rando velocity +im_ux = randn(n_windows); +im_uy = randn(n_windows); +% construct object space velocity using numerical approximation +im_pos_plus = im_pos + [im_ux(:) im_uy(:)]'/2; +im_pos_minus= im_pos - [im_ux(:) im_uy(:)]'/2; +obj_vel = p2d_map_im2obj(P, im_pos_plus) - p2d_map_im2obj(P, im_pos_minus); +obj_ux = reshape(obj_vel(1,:), n_windows); +obj_uy = reshape(obj_vel(2,:), n_windows); + +%% apply rescaling +V_im = struct('win_x', win_x, 'win_y', win_y, ... + 'ux', im_ux, 'uy', im_uy); +calib = struct('P', P); +V_obj = scale2d(V_im, calib, false); + +%% compare +rms(obj_ux(:) - V_obj.U(:)) +rms(obj_uy(:) - V_obj.V(:)) + +%% compare +figure(1); +clf; +plot(obj_ux(:), V_obj.U(:), '.'); +hold on; +plot(obj_uy(:), V_obj.V(:), '.'); +axis equal tight; + + + diff --git a/misc/xmlstr2struct.m b/misc/xmlstr2struct.m new file mode 100644 index 0000000000000000000000000000000000000000..79aed7eb765499b15c9ed168fc7ac8f29a04a957 --- /dev/null +++ b/misc/xmlstr2struct.m @@ -0,0 +1,28 @@ +function s = xmlstr2struct(xml) + % s = xmlstr2struct(xml) + % parse an XML string into a MATLAB structure + % + % Input: + % xml xml markup string + % Output: + % s MATLAB structure containing parsed output of xml markup + + % create a temporary file name + s = []; + fname = tempname; + try + %% create temporary file + fp = fopen(fname, 'w'); + fwrite(fp, xml); + fclose(fp); + + %% read xml + s = xml2struct(fname); + + %% delete + delete(fname); + catch + % delete temporary file + delete(fname); + end +end \ No newline at end of file diff --git a/sampledata/NACA0020_00000.jpg b/sampledata/NACA0020_00000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aeab5427f7ee8eedb512cb77d94b4a2c29ac7be0 Binary files /dev/null and b/sampledata/NACA0020_00000.jpg differ diff --git a/sampledata/NACA0020_00001.jpg b/sampledata/NACA0020_00001.jpg new file mode 100644 index 0000000000000000000000000000000000000000..43be1936f31e5d87212f660a2c5be0275ac61d00 Binary files /dev/null and b/sampledata/NACA0020_00001.jpg differ diff --git a/vector_postprocess.m b/vector_postprocess.m new file mode 100644 index 0000000000000000000000000000000000000000..b26d7dcf10c5939a80f3257a9fd28374af933332 --- /dev/null +++ b/vector_postprocess.m @@ -0,0 +1,44 @@ +function W = vector_postprocess(V, ops) + % apply postprocessing to vector field + % by sequentially applying operations in ops to V + % + % each element in ops has at least one member, 'type', which determines what + % type of filter is to be applied. it is a string which selects from a + % number of different available filters + % + % Type/member Default Description + % ---------------------------------------------------------------------- + % scale2d apply scaling from image to object space + % .P eye(3) 2D projection matrix representing mapping + % from image space to object space + % .keep false flag to indicate whether old pixel + % displacement data should be retained + % scaledt rescale PIV delta t + % .dt 1 new value of PIV delta-t to rescale data to + + %% iteratively apply filters + W = V; + n_ops = length(ops); + + for i = 1 : n_ops + operation = ops{i}; + if strcmp(operation.type, 'scale2d') + %% scale from image to object space + calib = struct('P', eye(3)); + keep = false; + if isfield(operation, 'P'), calib.P = operation.P; end + if isfield(operation, 'keep'), keep = operation.keep; end + W = scale2d(W, calib, keep); + elseif strcmp(operation.type, 'scaledt') + %% scale to new delta t + dt = 1; % scales to pixel displacement (unit delta t) + if isfield(operation, 'dt'), dt = operation.dt; end + W = scaledt(W, dt); + elseif strcmp(operation.type, 'null') + %% null operation, do nothing + else + error('unrecognised filter type %s', filter.type); + end + end +end + \ No newline at end of file