diff --git a/zUtil_Imaging/gtESF2LSF.m b/zUtil_Imaging/gtESF2LSF.m new file mode 100644 index 0000000000000000000000000000000000000000..dd89ecd54b3bdbf4e9ce7ed029fe98c8a90763fa --- /dev/null +++ b/zUtil_Imaging/gtESF2LSF.m @@ -0,0 +1,203 @@ +function lsf_line = gtESF2LSF(img, varargin) + conf = struct( ... + 'bb', [], 'direction', [], ... + 'size', 21, 'oversampling', 5, 'data_type', 'single', ... + 'side', 'both', 'use_astra', false, ... + 'keep_oversampling', true, 'verbose', true); + conf = parse_pv_pairs(conf, varargin); + + p = gtLoadParameters(); + + if (ischar(img)) + img = GtVolView.loadVolume(img); + elseif (numel(img) == 1) + filename = sprintf('refHST%04d.edf', img); + filename = fullfile(p.acq.dir, '0_rawdata', p.acq.name, filename); + img = edf_read(filename); + end + + if (isempty(conf.bb)) + bb_half_size = ceil(p.acq.bbdir(3:4) / 2); + bb = [p.acq.bbdir(1:2), bb_half_size]; + if (isempty(conf.direction) || strcmpi(conf.direction, 'top-bottom')) + bb(1) = bb(1) + ceil(bb_half_size(1) / 2); + bb(2) = bb(2) - ceil(bb_half_size(2) / 2); + elseif (strcmpi(conf.direction, 'left-right')) + bb(1) = bb(1) - ceil(bb_half_size(1) / 2); + bb(2) = bb(2) + ceil(bb_half_size(2) / 2); + elseif (strcmpi(conf.direction, 'right-left')) + bb(1) = bb(1) + 3 * ceil(bb_half_size(1) / 2); + bb(2) = bb(2) + ceil(bb_half_size(2) / 2); + else + bb(1) = bb(1) + ceil(bb_half_size(1) / 2); + bb(2) = bb(2) + 3 * ceil(bb_half_size(2) / 2); + end + bb(3:4) = bb(3:4) + 1 - mod(bb(3:4), 2); + + conf.bb = get_roi_bb(img, 'ESF Bounding Box', bb); + end + + if (isempty(conf.direction)) + directions = {'left-right', 'right-left', 'top-bottom', 'bottom-top'}; + format_dirs = {1, 2, 3, 4; directions{:}}; + questionMsg = ['What is the rough direction of the edge (dark -> bright)?' sprintf('\n%d) %s', format_dirs{:}) '\n']; + dir_num = inputwdefault(questionMsg, ''); + while (~ismember(dir_num, {'1', '2', '3', '4'})) + dir_num = inputwdefault(questionMsg, ''); + end + conf.direction = directions{str2double(dir_num)}; + end + + if (conf.oversampling == 1) + edge_img = img(conf.bb(2):(conf.bb(2)+conf.bb(4)-1), conf.bb(1):(conf.bb(1)+conf.bb(3)-1)); + else + shift = (1 - 1/conf.oversampling) / 2; + vv = linspace(conf.bb(2) - shift, conf.bb(2) + conf.bb(4) - 1 + shift, conf.bb(4) * conf.oversampling); + uu = linspace(conf.bb(1) - shift, conf.bb(1) + conf.bb(3) - 1 + shift, conf.bb(3) * conf.oversampling); + [uu, vv] = ndgrid(uu, vv); + edge_img = interp2(img, uu, vv, 'spline', 0); + end + + edge_img = cast(edge_img, conf.data_type); + + if (ischar(conf.direction)) + conf.direction = find_edge_angle(edge_img, conf.direction, true); + end + + if (conf.use_astra) + else + angle = atan2d(conf.direction(2), conf.direction(1)); + rot_edge_img = gtRotateVolume(edge_img, -angle); + end + + sum_esf = sum(rot_edge_img, 2)'; + lsf = diff(sum_esf); + grad_pos = (1:numel(lsf)) + 0.5; + [max_lsf_val, max_lsf_pos] = max(lsf); + rel_max_lsf_pos = max_lsf_pos + 0.5; + + if (conf.verbose) + f = figure(); + ax = axes('parent', f); + plot(ax, 1:numel(sum_esf), sum_esf) + hold(ax, 'on') + plot(ax, grad_pos, lsf) + plot(ax, rel_max_lsf_pos, max_lsf_val, 'o') + hold(ax, 'off') + end + + % Finding avg point + lsf_edge = (conf.size * conf.oversampling - 1) / 2; + inds = max_lsf_pos-5:max_lsf_pos+5; + center_pos = sum(lsf(inds) .* inds) / sum(lsf(inds)); + + lsf = interp1(lsf, linspace(center_pos-lsf_edge, center_pos+lsf_edge, conf.size * conf.oversampling), 'spline'); + switch (conf.side) + case 'both' + case 'left' + lsf(lsf_edge+2:end) = flip(lsf(1:lsf_edge)); + case 'right' + lsf(1:lsf_edge) = flip(lsf(lsf_edge+2:end)); + end + lsf(lsf < 0) = 0; + if (~conf.keep_oversampling) + lsf = reshape(lsf, conf.oversampling, conf.size); + lsf = sum(lsf, 1); + end + lsf = lsf / sum(lsf); + + if (conf.verbose) + f = figure(); + ax = axes('parent', f); + plot(ax, lsf) + end + + c = reshape(fieldnames(conf), 1, []); + c(2, :) = struct2cell(conf); + lsf_line = struct('data', lsf, c{:}); +end + +function bb = get_roi_bb(img, title, bb) + hfig = figure('name', title); + + colormap(hfig, gray); + ax = axes('parent', hfig); + imagesc(img, 'parent', ax); + drawnow(); + hold(ax, 'on'); + + plot([bb(1), bb(1)+bb(3)-1, bb(1)+bb(3)-1, bb(1), bb(1)], [bb(2), bb(2), bb(2)+bb(4)-1, bb(2)+bb(4)-1, bb(2)], 'r-'); + bb_center = bb(1:2) + (bb(3:4) - 1) / 2; + plot([bb_center(1) bb_center(1)], [bb(2) bb(2)+bb(4)-1], 'c-.'); + plot([bb(1), bb(1)+bb(3)-1], [bb_center(2) bb_center(2)], 'c-.'); + + % interactive check + questionMsg = 'Are you satisfied with this bounding box for the region where to compute the ESF sampling? [y/n]'; + while (strcmpi(inputwdefault(questionMsg, 'y'), 'n')) + disp('Please select (zoom onto) the region where to compute the ESF and disable zoom afterwards'); + + figure(hfig), clf; + ax = axes('parent', hfig); + imagesc(img, 'parent', ax); + h = zoom(hfig); + set(h, 'Enable', 'on'); + waitfor(h, 'Enable', 'off') + hold(ax, 'on'); + disp('-> Now click top-left and bottom-right corners for final selection'); + bb = ginput(2); + bb = round([bb(1, 1), bb(1, 2), bb(2, 1)-bb(1, 1)+1, bb(2, 2)-bb(1, 2)+1]); + + % Make sure the bbox width and height are odd numbers (needed for correlation) + bb(3:4) = bb(3:4) + 1 - mod(bb(3:4), 2); + + % Plot the new bounding box + plot([bb(1), bb(1)+bb(3)-1, bb(1)+bb(3)-1, bb(1), bb(1)], [bb(2), bb(2), bb(2)+bb(4)-1, bb(2)+bb(4)-1, bb(2)], 'r-'); + bb_center = bb(1:2) + (bb(3:4) - 1) / 2; + plot([bb_center(1) bb_center(1)], [bb(2) bb(2)+bb(4)-1], 'c-.'); + plot([bb(1), bb(1)+bb(3)-1], [bb_center(2) bb_center(2)], 'c-.'); + end + + close(hfig); + + fprintf('Chosen BB: [%s]\n', sprintf(' %d', bb)) +end + +function [direction, angle] = find_edge_angle(edge_img, rough_dir, do_plot) + % We rotate the roi, to facilitate the computation + switch (rough_dir) + case 'left-right' + base_k_90_rot = 0; + case 'right-left' + base_k_90_rot = 2; + case 'top-bottom' + base_k_90_rot = 3; + case 'bottom-top' + base_k_90_rot = 1; + otherwise + error('gtESF2LSF:wrong_argument', ... + 'Direction "%s" unknown', rough_dir); + end + edge_img = rot90(edge_img, base_k_90_rot); + + % We will now use the derivative of all the lines, to determine the + % angle + grads = diff(edge_img, 1, 1); + [~, max_grads_pos] = max(grads, [], 1); + + % least square fitting of the tilt angle + X = [ones(numel(max_grads_pos), 1), (1:numel(max_grads_pos))']; + vals = X \ reshape(max_grads_pos, [], 1); + + if (exist('do_plot', 'var') && do_plot) + f = figure(); + ax = axes('parent', f); + plot(ax, max_grads_pos) + hold(ax, 'on') + plot(ax, (1:numel(max_grads_pos))', X*vals); + hold(ax, 'off') + end + + angle = base_k_90_rot * 90 + atand(vals(2)); + + direction = [cosd(angle), sind(angle)]; +end diff --git a/zUtil_Imaging/gtLSFs2PSF.m b/zUtil_Imaging/gtLSFs2PSF.m new file mode 100644 index 0000000000000000000000000000000000000000..23d909297c257d59dd660c1403e4d640b20d591b --- /dev/null +++ b/zUtil_Imaging/gtLSFs2PSF.m @@ -0,0 +1,95 @@ +function psf = gtLSFs2PSF(lsfs, varargin) + conf = struct('method', 'fourier'); + conf = parse_pv_pairs(conf, varargin); + + num_lsfs = numel(lsfs); + + if (num_lsfs == 1) + % We're assuming here that the only LSF given is the radial + % component of a radially symmetric PSF + data = reshape(lsfs.data, [], 1); + switch (lower(conf.method)) + case 'astra' + case 'iradon' + data = data(:, ones(180, 1)); + psf_inner = iradon(data, 1:180,'linear', 'Shepp-Logan'); + psf_upsize = [numel(lsfs.data), numel(lsfs.data)]; + psf = zeros(psf_upsize, lsfs.data_type); + shifts = (psf_upsize - 1) / 2 - size(psf_inner) / 2 + 1; + psf(1+shifts(1):size(psf_inner, 1)+shifts(1), 1+shifts(2):size(psf_inner, 2)+shifts(2)) = psf_inner; + case 'fourier' + data_f = ifftshift(data); + data_f = fft(data_f); + data_f = fftshift(data_f); + + psf_edge = (numel(lsfs.data) - 1) / 2; + [xx, yy] = ndgrid(-psf_edge:psf_edge, -psf_edge:psf_edge); + rr = sqrt(xx .^ 2 + yy .^ 2); + psf_f = interp1(-psf_edge:psf_edge, data_f, rr, 'spline', 0); + + psf_f = ifftshift(psf_f); + psf = ifft2(psf_f); + psf = real(fftshift(psf)); + case 'hankel' + end + else + % Checking compatibility + if (any(lsfs(1).size ~= [lsfs(2:end).size])) + error('gtPSFEstimateFromRef:wrong_argument', ... + 'All LSFs should have the same size'); + end + if (any([lsfs.keep_oversampling])) + if (any(lsfs(1).keep_oversampling ~= [lsfs(2:end).keep_oversampling])) + error('gtPSFEstimateFromRef:wrong_argument', ... + 'All LSFs should behave the same regarding oversampling'); + end + if (any(lsfs(1).oversampling ~= [lsfs(2:end).oversampling])) + error('gtPSFEstimateFromRef:wrong_argument', ... + 'All LSFs should have the same oversampling'); + end + end + + % The LSFs given here, are Fourier samplings of the 2D PSF + data = cat(1, lsfs.data); + + data_f = ifftshift(data, 2); + data_f = fft(data_f, [], 2); + data_f = fftshift(data_f, 2); + + psf_edge = (size(data, 2) - 1) / 2; + + dirs = cat(1, lsfs.direction); + angles = mod(atan2d(dirs(:, 2), dirs(:, 1)), 360); + + % Let's bring everything in a polar re-arrangement + data_f = [data_f(:, 1+psf_edge:end); flip(data_f(:, 1:1+psf_edge), 2)]; + angles = [angles; mod(angles - 180, 360)]; + + % let's re-order them + [angles, inds] = sort(angles); + data_f = data_f(inds, :); + + % Let's wrap around: we need one going to more than 180, and one + % going to less than -180, and left and right sides have to be + % treated as radial + angles = [angles(end) - 360; angles; angles(1) + 360]; + data_f = [data_f(end, :); data_f; data_f(1, :)]; + + [xx, yy] = ndgrid(-psf_edge:psf_edge, -psf_edge:psf_edge); + rr = sqrt(xx .^ 2 + yy .^ 2); + aa = mod(atan2d(yy, xx), 360); + + [aa_base, rr_base] = ndgrid(angles, 0:psf_edge); + psf_f = interp2(rr_base, aa_base, data_f, rr, aa, 'spline', 0); + + psf_f = ifftshift(psf_f); + psf = ifft2(psf_f); + psf = real(fftshift(psf)); + end + + if (lsfs(1).keep_oversampling) + psf = reshape(psf, lsfs(1).oversampling, lsfs(1).size, lsfs(1).oversampling, lsfs(1).size); + psf = squeeze(sum(sum(psf, 1), 3)); + end + psf = psf / sum(psf(:)); +end