Steve on Image Processing

Revisiting dctdemo – part 2 4

Posted by Steve Eddins,

This is the second part of my plan to rewrite an Image Processing Toolbox example from 20 years ago using more modern MATLAB language features.

Recall the old app I'm trying to reinvent:

The idea is to experiment with the basic principles of DCT-based image compression. I've decided that I don't want to copy this original layout and functionality exactly. For one thing, computers (and MATLAB!) are a lot faster than they were 20 years ago, and I think we really don't need a separate "Apply" button.

Here's my first attempt at sketching what I'd like to make this time around.

The truth, is, I'm making the code and these posts up as I go along. So far I've only had a chance to get some of the initial pieces up and working. But it's enough to start exploring some of the coding ideas.

Here's what happens when I call the app class, which I've called (for now) dctCompressionExample_v2.

app = dctCompressionExample_v2
app = 

  dctCompressionExample_v2 handle

  Properties:
         OriginalImage: [240x240 double]
    ReconstructedImage: []
            ErrorImage: []
    DCTCoefficientMask: []
    NumDCTCoefficients: 10


You can see I got some output in the Command Window, and I also got a new figure on the screen.

Here's the code so far.

classdef dctCompressionExample_v2 < handle
    properties (SetAccess = private)
        OriginalImage
        ReconstructedImage
        ErrorImage
        DCTCoefficientMask
    end
    properties
        NumDCTCoefficients = 10
    end
    properties (Access = private)
        Slider
        OriginalImageAxes
        ReconstructedImageAxes
        ErrorImageAxes
        MaskAxes
    end
    methods
        function this_app = dctCompressionExample_v2
            this_app.OriginalImage = initialImage;
            layoutApp(this_app);
            update(this_app);
        end
        function layoutApp(app)
            f = figure;
            app.OriginalImageAxes = axes('Parent',f, ...
                'Position',[0.12 0.56 0.28 0.28]);
            app.ReconstructedImageAxes = axes('Parent',f, ...
                'Position',[0.60 0.56 0.28 0.28]);
            app.MaskAxes = axes('Parent',f, ...
                'Position',[0.12 0.16 0.28 0.28]);
            app.ErrorImageAxes = axes('Parent',f, ...
                'Position',[0.60 0.16 0.28 0.28]);
            app.Slider = uicontrol('Style','slider', ...
                'Value',app.NumDCTCoefficients/64, ...
                'Units','normalized', ...
                'Position',[0.12 0.08 0.28 0.04], ...
                'Callback',@(~,~,~) app.reactToSliderChange);
        end
        function reactToSliderChange(app)
            v = get(app.Slider,'Value');
            app.NumDCTCoefficients = round(64*v);
            update(app);
        end
        function update(app)
            imshow(app.OriginalImage,'Parent',app.OriginalImageAxes);
            title(app.OriginalImageAxes,'Original Image');
            imshow(app.OriginalImage,'Parent',app.ReconstructedImageAxes);
            title(app.ReconstructedImageAxes,'Reconstructed Image');
            imshow(zeros(size(app.OriginalImage)),'Parent',app.ErrorImageAxes);
            title(app.ErrorImageAxes,'Error Image');
            imshow(zeros(size(app.OriginalImage)),'Parent',app.MaskAxes);
            title(app.MaskAxes,'DCT Coefficient Mask');
            drawnow;
        end
    end
end
function I = initialImage
pout = imread('pout.tif');
pout2 = pout(1:240,:);
I = im2double(adapthisteq(pout2));
end

At this point I'll remind you of Dave Garrison's article that I mentioned last time. This article teaches about how this kind of code works. I don't intend to repeat all of that article here. I'll just point out a few things.

Here's the function that runs when you launch the app.

        function this_app = dctCompressionExample_v2
            this_app.OriginalImage = initialImage;
            layoutApp(this_app);
            update(this_app);
        end

initialImage is a little function I stuck at the bottom of the file. It's purpose is to create our sample image, which for the purpose of this simple DCT demonstration needs to be a multiple of 8-by-8 in size. (Plus, I threw in a call to adapthisteq because the original image has low contrast.)

function I = initialImage
pout = imread('pout.tif');
pout2 = pout(1:240,:);
I = im2double(adapthisteq(pout2));
end

Then there's the properties block.

    properties (SetAccess = private)
        OriginalImage
        ReconstructedImage
        ErrorImage
        DCTCoefficientMask
    end
    properties
        NumDCTCoefficients = 10
    end
    properties (Access = private)
        Slider
        OriginalImageAxes
        ReconstructedImageAxes
        ErrorImageAxes
        MaskAxes
    end

Some of the properties, such as OriginalImage, I want to make accessible but read-only. Some of them, such as OriginalImageAxes, are private because they are only needed by the guts of the app. Notice that one of them, NumDCTCoefficients, is both readable and settable. I wanted to explore the idea of giving this app a little programmable interface so that you can write code to make it change.

I would like to make it so that the NumDCTCoefficients property changes when you drag the slider. Here's how to accomplish that:

            app.Slider = uicontrol('Style','slider', ...
                'Value',app.NumDCTCoefficients/64, ...
                'Units','normalized', ...
                'Position',[0.12 0.08 0.28 0.04], ...
                'Callback',@(~,~,~) app.reactToSliderChange);

Setting the 'Callback' property of the slider like this causes the app's function reactToSliderChange to get called whenever the slider is dragged. Here's what reactToSliderChange does:

        function reactToSliderChange(app)
            v = get(app.Slider,'Value');
            app.NumDCTCoefficients = round(64*v);
            update(app);
        end

Now we're starting to get a little interactivity going in our app.

That's about as far as I got this week. Next time we'll continue wiring things up and maybe start putting in some algorithmic DCT block processing code.


Get the MATLAB code

Published with MATLAB® R2012b

4 CommentsOldest to Newest

Hi Steve,

I like the idea of using an object oriented approach to GUI coding. I have one question, however. I don’t want that the class constructor returns this_app to the base workspace, when I call the constructor from the command line. Ok, it will be there since it is a handle, but it shouldn’t show up. At the moment, when calling my gui without output argument, I will receive the instance of the class as ans.

Any idea how to avoid this? My usual approach

function y = myfun(x);

yt = somefun(x);
if nargout == 1;
y = yt;
end

won’t work with classdef.

Thanks, Anon

Anon—I don’t think there’s a way to avoid it with just one MATLAB code file. You can do it with two files: an ordinary function file and a classdef file.

Steven,

Do you have any suggestions for deleting the object once the figure is closed; on the converse, closing the figure when the object is deleted?

-Justin

Justin—In the CloseRequestFcn callback of the figure, you can call delete on the object handle. This won’t delete the object from your workspace, but it will display as “deleted handle object” (or something like that), and you won’t be able to do anything with it.

It’s harder to do the other way around. There are multiple references to the handle object. One is in your workspace, but others are bound into the function handles that are saved as callback functions on the app. The destructor for the app handle object won’t get called until the last reference is deleted.

These postings are the author's and don't necessarily represent the opinions of MathWorks.