Steve on Image Processing

August 7th, 2007

Upslope area - Forming and solving the flow matrix

About twelve years ago, I was implementing the algorithm that became roifill in the Image Processing Toolbox. In this algorithm, a specified set of pixels is replaced (or filled in) such that each of the replaced pixels equals the average of its north, east, south, and west neighbors. I had implemented an iterative algorithm, which worked but was slow. When I asked MATLAB creator and company cofounder Cleve if he knew a better way, he immediately replied, "It looks like a sparse linear system to me. Why don't you just use backslash?"

Head slap! Of course.

Now here I am again, looking at a very similar set of equations and thinking again about using sparse backslash.

In the Tarboton paper, the upslope area calculation is formulated recursively. That is, the upslope area of a pixel equals 1 plus some fraction of the upslope area of its neighboring pixels. (The specific fraction is a function of the flow direction for the neighbors.) So that's a linear system of equations. Let's see how to set up and solve the system in MATLAB.

We'll start by making a test "DEM" to work with. I like using peaks.

E = peaks;
imshow(E, [], 'InitialMagnification', 'fit')

Next, compute the pixel flow directions, using the function dem_flow from my upslope area contribution on the MATLAB Central File Exchange.

R = dem_flow(peaks);

Each row and column of our sparse system will correspond to an individual pixel. It will be convenient to make a matrix, the same size as E, that "numbers" or labels the individual pixels of E. These labels will be used as the sparse matrix row and column coordinates.

pixel_labels = 1:numel(E);
pixel_labels = reshape(pixel_labels, size(E));
pixel_labels(1:4, 1:6)
ans =

     1    50    99   148   197   246
     2    51   100   149   198   247
     3    52   101   150   199   248
     4    53   102   151   200   249

Let's consider north neighbors. Here's the set of pixels with north neighbors:

pixels_with_north_neighbors = pixel_labels(2:end, :);

And here are the corresponding north neighbors:

north_neighbors = pixel_labels(1:end-1, :);

The angle pointing inward from a pixel's north neighbor to itself is -pi/2:

inward_angle = -pi/2;

A pixel's north neighbor has a flow that points in a particular direction. That direction is contained in R. If the flow direction is the same as the inward angle, then the fraction of flow from that neighbor is 1.0. If the flow direction is pointing more than pi/4 radians away from the inward angle, then the fraction of flow from that neighbor is 0.0. For inbetween angular differences, the fraction varies linearly from 1.0 to 0.0.

I have written simple functions to compute angular differences and the corresponding weights. The angular difference computation looks like this:

 d = mod(theta1 - theta2 + pi, 2*pi) - pi;

and the directional weight computation looks like this:

 w = max(1 - abs(d)*4/pi, 0);

With these functions, we can compute the north neighbor weights for all the pixels at once:

w = directional_weight(angular_difference(R(north_neighbors), ...
    inward_angle));

Ignoring the other neighbors for the moment, the upslope area equation looks like this:

  Ap = 1 + wAn

where Ap is the upslope area of the pixel, An is the upslope area of the north neighbor, and w is the directional weight based on the flow direction of the north neighbor. Rewrite the equation this way to see how to construct the linear equation matrix:

 Ap - wAn = 1

So each row of our matrix is going to have a 1 on the diagonal, and it's going to have the negative of the directional flow weights for each of its neighbors. If we used only the northern weights computed above, we could construct the sparse matrix this way:

i = pixels_with_north_neighbors(:);
j = north_neighbors(:);
w = w(:);

% Add diagonal terms
ii = [i ; pixel_labels(:)];
jj = [j ; pixel_labels(:)];
ww = [w ; ones(numel(pixel_labels), 1)];

% And construct the linear system matrix with a call to sparse
T = sparse(ii, jj, ww);
spy(T)

We have to zoom in to clearly see the off-diagonal terms:

xlim([0 20])
ylim([0 20])

I'm going to call T the flow matrix. To finish computing T, we'd have to repeat some of the steps above for each of the seven other neighbor directions, which is way too tedious to reproduce here. I'll just use the flow_matrix function in my MATLAB Central File Exchange submission:

T = flow_matrix(E, R);

Now we can compute the upslope area of each pixel by solving the matrix equation:

  Ta = b

where T is the sparse matrix we just formed, a is the vector of upslope areas for every pixel, and b is a vector of ones.

a = T \ ones(numel(E), 1);
A = reshape(a, size(E));

imshow(A, [], 'InitialMagnification', 'fit')

I have found that using log(A) works well for visualizing an upslope area matrix:

imshow(log(A), [], 'InitialMagnification', 'fit')

The brighter values have the highest upslope areas.

In my next post in this series, I'll revisit the issue of handling plateaus.


Get the MATLAB code

Published with MATLAB® 7.4

7 Responses to “Upslope area - Forming and solving the flow matrix”

  1. PISAL replied on :

    Dear Sir,
    I have been following and learning MATLAB alot from ur blog. I found it very useful.
    Currently, I have been assigned to do a project regarding the transforming the signal into 2-D grayscale displays. I have alot difficulties in doing this project. Would u mind giving me some idea that might be useful to me?
    Ur idea is really appreciated? THanks
    With Regard,

  2. Steve replied on :

    Pisal—Can you be more specific? “transforming the signal into 2-D grayscale displays” is pretty vague.

  3. Tim Davis replied on :

    In flow_matrix.m, you do a nice job of constructing the sparse matrix. But I’m curious as to why you work so hard to remove zero weights from the vv array prior to passing it to “sparse”.

    T=sparse(ii,jj,vv) will do that for you. Just change

    non_zero_weight = p ~= 0;

    ii = [ii; i(non_zero_weight)]; %#ok
    jj = [jj; j(non_zero_weight)]; %#ok
    vv = [vv; p(non_zero_weight)]; %#ok

    to

    ii = [ii ; i] ; %#ok
    jj = [jj ; j] ; %#ok
    vv = [vv ; p] ; %#ok

    and in doing that, ii, jj, vv are now of a known size a priori, and can be preallocated as a side benefit.

    The main benefit is that the code is simpler … no need to do the work when “sparse” is doing it for you. I doubt that this is a speed issue, just a clarity one.

  4. Tim Davis replied on :

    T is an interesting matrix. It’s “morally triangular”: a permutation of a triangular matrix, which means that a=T\b just permutes and does a forward or backsolve (which one depends on details inside x=A\b I’m unaware of).

    Is T morally triangular because of the direction of the flow always means (at least for “peaks”) that there is never a cycle in the graph?

    spparms(’spumoni’,2)
    a = T \ ones(numel(E), 1);
    sp\: bandwidth = 50+1+50.
    sp\: is A diagonal? no.
    sp\: is band density (0.03) > bandden (0.50) to try banded solver? no.
    sp\: is A triangular? no.
    sp\: is A morally triangular? yes.
    sp\: permute and solve.

  5. Steve replied on :

    Tim—In an earlier version of flow_matrix.m, I was constructing the ii, jj, and w vectors as you suggest. The problem is that it took too much memory. I was running out of memory on image sizes that I wanted the code to be able to handle. Removing the zero-valued elements as the vectors are built up reduces the memory requirement significantly, because in the upslope area calculation most pixels receive flow from only 2-3 of their 8 neighbors.

  6. Steve replied on :

    Tim—”Morally triangular” is the funniest technical phrase I’ve heard in a while! I believe that as long as plateaus are handled correctly, the fact that water flows only downhill means that there are no cycles in the graph.

  7. Tim Davis replied on :

    That would make the matrix morally triangular, or at least psychologically triangular.

    Cleve Moler defined a “morally triangular” matrix as a matrix A for which there exists a permutation P such that P*A is lower triangular. John Gilbert defined a “psychologically triangular” matrix as a full rank matrix A such that P*A*Q is upper or lower triangular for some P and Q. These are just *very* informal definitions … but the phrase did make it into the “spumoni” output. John is very pleased that none of these definitions have caught on.

    For either case, it’s quite simple to find the P and Q, if they exist. The check done each and every time x=A\b occurs when A is sparse (and not diagonal, banded, or triangular) which you can see with the spparms(’spumoni’,2) command, followed by x=A\b for sparse A. The algorithm to detect moral/psycho triangularity is a homework exercise in my sparse book. It’s a fun problem … there’s a nifty algorithm behind it. And in honor of John and Cleve I *didn’t* mention the phrase “morally triangular” or “psychologically triangular” in the book!

    Now, if you want to know what a “Horror Matrix” is … just Google “horror matrices” and click “I’m feeling lucky” ;-). Some matrices are a horror to direct methods, and others a horror for iterative methods.

Leave a Reply

Wrap code fragments inside <pre> tags, like this:

<pre class="code">
a = magic(3);
sum(a)
</pre>

If you have a "<" character in your code, either follow it with a space or replace it with "&lt;" (including the semicolon).


Steve Eddins manages the Image & Geospatial development team at The MathWorks and coauthored Digital Image Processing Using MATLAB. He writes here about image processing concepts, algorithm implementations, and MATLAB.

  • Sana: hi steve, could you explain to me how i would be able to use the dir function, to do a loop through a directory...
  • Nishtha: Sir, I have preprocessed the image in following steps: [1] adaptive histogram equalization [2] thresholding...
  • Kristof: I also strongly support the idea. I have just recently bumped into the problem that im2single was not...
  • Steve: David—I’ m glad you found it useful!
  • David Lalejini: I found your example very useful for finding connected nodes in a large set of input pairs. I start...
  • tommy: Dear Steve, I have a question,please if you are kind to help me regarding the accumulator array dimensions of...
  • Steve: Abc—I don’t know how to distinguish the faces. You might try posting your question in the MATLAB...
  • Manju: well if we have a few ovals within each other like in a cell how do we measure the distance from the center...
  • Steve: Manju—What do you mean? How is each region defined?
  • Manju: if we have 2-3 regions within each other how do we measure the regions of each one?

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