function mod10_01
  % Effect of Lowpass Filtering an Image
  %
  % This Octave/MATLAB script was written to mimic a corresponding
  % Labview module (Module 10.1) that accompanies the free text 
  % "Signals and Systems: Theory and Applications" by Ulaby and Yagle
  % (ss2.eecs.umich.edu).
  %
  % In almost all cases the compuation code used in these scripts
  % is the same as that used in the Labview module.  Credit for the
  % computation code should go to the textbook authors.
  
  % These scripts have been tested under Octave 4.4.0 and
  % MATLAB R2018a.  The Octave GUI implementation is still a
  % little buggy.  Some of strange plotting code used here attempts
  % to work around a few Octave bugs.

  % Copyright (C) 2018 Anthony Richardson <richardson.tony@gmail.com>
  %
  % This program is free software; you can redistribute it and/or modify it under
  % the terms of the GNU General Public License as published by the Free Software
  % Foundation; either version 3 of the License, or (at your option) any later
  % version.
  %
  % This program is distributed in the hope that it will be useful, but WITHOUT
  % ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  % FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  % details.
  %
  % You should have received a copy of the GNU General Public License along with
  % this program; if not, see <http://www.gnu.org/licenses/>.
  %
  
  % Delay
  K1 = 20;
  
  % Noise level
  K2 = 20;
  
  % Save initial simulation state
  mod10_01_state('K1', K1);
  mod10_01_state('K2', K2);
  mod10_01_state('firstcall', true);
  
  clf;
  mod10_01_gui();
  mod10_01_sim();
end

function mod10_01_gui
  %%{
  pos = [0 0.87 1 .13 ];
  cpanel = uipanel ('position', pos);
  mod10_01_state('cpanel', cpanel);
  
  %%{
  %% BUG workaround - may be able to remove this in a
  %% future version of octave
  if (exist('OCTAVE_VERSION', 'builtin'))
    % The panel position seems to get messed up when the
    % window is resized in Octave. This was an attempt to fix it.
    %set(gcf, 'sizechangedfcn', @resetControlPanel);
    % That didn't work but this seems too.
    dellistener(gcf, 'position');
    addlistener (gcf, 'position', {@resetControlPanel, cpanel, pos});
  end
  %}
  h = sliderControl(cpanel, 'K1', [10, 128], ...
                      mod10_01_state('K1'), [2 10], ...
                      [0.165 0 .28 1], @mod10_01_cb );
  mod10_01_state('K1h', h);
  %%{
  h = sliderControl(cpanel, 'K2', [10, 128], ...
                      mod10_01_state('K2'), [2  10], ...
                      [0.545 0  .28 1], @mod10_01_cb );
  mod10_01_state('K2h', h);
  %}
end

function mod10_01_cb(s, ~)
  g = mod10_01_state();
  if (s == g.K1h)
    % K1 slider  
    v = floor(get(s, 'value')/2)*2;
    set(s, 'value', v);
    set(get(s, 'userdata'), 'string', num2str(v));
    mod10_01_state('K1', v);
  elseif (s == g.K2h)
    % K2 slider  
    v = floor(get(s, 'value')/2)*2;
    set(s, 'value', v);
    set(get(s, 'userdata'), 'string', num2str(v));
    mod10_01_state('K2', v);
  end
  
  mod10_01_sim();
end

function mod10_01_sim()

  %K1i = mod10_01_state('K1') + 1;
  %K2i = mod10_01_state('K2') + 1;
  K1i = mod10_01_state('K1');
  K2i = mod10_01_state('K2');
  
  firstcall = mod10_01_state('firstcall');

  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % Calculate all of the data
  persistent x M N;
  if isempty(x)
    x = uint8(255*imread('letters.bmp'));  
    %x = imread('letters.bmp');  
    [M, N] = size(x);
    %[min(min(x))  max(max(x))]
  end
  
  persistent FX1 FXX1i FXX1min FXX1max;
  if isempty(FXX1i)
    FX1 = fft2(x);
    %[min(min(abs(FX1))) max(max(abs(FX1)))]
    FXX1 = log(abs(fftshift(FX1)));
    FXX1min = min(min(FXX1));
    FXX1max = max(max(FXX1));
    FXX1i = uint8(255*(FXX1 - FXX1min)/(FXX1max - FXX1min));
  end
  
  FX2 = FX1;
  %FX2(K1i:M+2-K1i, :) = 0;
  %FX2(:, K2i:N+2-K2i) = 0;
  FX2(K1i+1:M-K1i, :) = 0;
  FX2(:, K2i+1:N-K2i) = 0;
  %[min(min(abs(FX2))) max(max(abs(FX2)))]
  y = real(ifft2(FX2));
  FXX2 = log(abs(fftshift(FX2)));
  FXX2(FXX2<FXX1min) = FXX1min;
  % Use the same scaling as for FXX1
  FXX2i = uint8(255*(FXX2 - FXX1min)/(FXX1max - FXX1min));
  
  % Now create the plots
  dw = 0.01; plotw = 0.37;  
  dh = 0.01; ploth = plotw; 
  plotrowy = [0 ploth+6*dh ] + dh;
  plotcolx = [0 plotw ] + 12.0*dw;

  % Create a common colormap for Octave and MATLAB
  mapsize = 128; 
  smapsize = floor(mapsize/3); 
  omap = [ zeros(1,mapsize-smapsize) (1:smapsize)/smapsize; 
              zeros(1,mapsize-2*smapsize) (1:2*smapsize)/(2*smapsize);
              (0:mapsize-1)/(mapsize-1)]';  
  % MATLAB 2018a does not seem to support different colormaps
  % for different subplots.  Here is a hack to work around the issue.
  cmap = [gray(128); omap];
  if (firstcall)
    % These plots never change, so only plot them once
    % Plot the original image
    subplot('position', [plotcolx(1) plotrowy(1) plotw ploth]);
    imshow((x+1)/2-1,cmap);
    h = title('Original image');
    p = get(h, 'position');
    xl = xlim();
    set(h, 'position', [xl(1) p(2:3)], 'horizontalalignment', 'left');

    % Plot the original spectrum
    subplot('position', [plotcolx(1) plotrowy(2) plotw ploth]);
    imshow(FXX1i/2+128,cmap);
    h = title('Original spectrum');
    p = get(h, 'position');
    set(h, 'position', [0 p(2:3)], 'horizontalalignment', 'left');
    %}
    
    % Show colormap strip for reference
    subplot('position', [0.04 plotrowy(2) 0.08 ploth]);
    imshow(uint8((128:255)'*ones(1,30)),cmap);
    set(gca, 'visible', 'on', 'ydir', 'normal');
    set(gca, 'xtick', [], 'ytick', []);
    
    mod10_01_state('firstcall', false);
  end

  % These change with every K1/K2 change
  % Plot the filtered image
  %sum(sum(abs(x-y)))
  subplot('position', [plotcolx(2) plotrowy(1) plotw ploth]);
  %imshow(uint8(floor(y/2)),cmap);
  imshow((y+1)/2-1,cmap);
  h = title('Filtered image');
  p = get(h, 'position');
  set(h, 'position', [0 p(2:3)], 'horizontalalignment', 'left');

  % Plot the filtered spectrum
  subplot('position', [plotcolx(2) plotrowy(2) plotw ploth]);
  imshow(FXX2i/2+128,cmap);
  h = title('Filtered Spectrum');
  p = get(h, 'position');
  set(h, 'position', [0 p(2:3)], 'horizontalalignment', 'left');
  %}
    
  drawnow();
end

function v = mod10_01_state(key, val)
  persistent state;
  if (nargin == 0)  
    % Return the entire struct
    v = state;
  elseif (nargin == 1)
    % Return a particular field from the struct
    v = state.(key);
  elseif (nargin == 2)
    % Set a key value (key must be a string)
    state.(key) = val;
    v = val;
  end
end

% Reset GUI panel after window size change
function resetControlPanel(~, ~, p, pos)
  % Just setting the panel position does not seem to work but
  % maximizing it and then resetting the position does.
  set(p, 'visible', 'off');
  set(p, 'position', [0 0 1 1]);
  set(p, 'position', pos);
  set(p, 'visible', 'on');
  drawnow();
end

function h = sliderControl(parent, label, limits, val, step, pos, cback)
  % sliderstep = [minstep maxstep]
  % where minstep is fraction of range on arrow click
  % and maxstep is fraction of range on trough click
  h = uicontrol(parent, 'style' ,'slider',...
           'units', 'normalized',...
           'position', [pos(1) pos(2)+0.33*pos(4) pos(3) 0.32*pos(4)],...
           'min', limits(1), 'max', limits(2),...
           'sliderstep', [step(1)/(limits(2)-limits(1)) step(2)/(limits(2)-limits(1))],...
           'value', val, 'callback', cback);
  uicontrol(parent, 'style', 'text',...
           'units', 'normalized',...
           'position', [pos(1) pos(2) 0.2*pos(3) 0.3*pos(4)],...
           'horizontalalignment', 'left', ...
           'String', num2str(limits(1)));
  uicontrol(parent, 'style', 'text',...
           'units', 'normalized',...
           'position', [pos(1)+0.8*pos(3) pos(2) 0.2*pos(3) 0.3*pos(4)],...
           'horizontalalignment', 'right', ...
           'String', num2str(limits(2)));         
  uicontrol(parent, 'style', 'text',...
           'units', 'normalized',...
           'position', [pos(1) pos(2)+0.67*pos(4) pos(3) 0.3*pos(4)],...
           'horizontalalignment','left',...
           'String', label);        
  hc = uicontrol(parent, 'style', 'text',...
           'units', 'normalized',...
           'position', [pos(1)+.3*pos(3) pos(2) 0.4*pos(3) 0.3*pos(4)],...
           'backgroundcolor', [0.8 0.8 0.8], ...
           'String', num2str(val));
  set(h, 'userdata', hc);
end
