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.

  • Steve: Kezia—Try imrotate.
  • kezia: steve, how to perform rotation of structuring element by 15 degrees. kindly answer my question. thank u kezia...
  • Steve: Tasha—I only accept comments that are relevant to the particular blog post or are questions or comments...
  • Tasha: Steve,I send you a comment here but still didn’t get any reply yet.I did not see my comment posted here...
  • Steve: Carsten—Thanks for your input.
  • Carsten: Another vote for either imtranslate.m, or at least a blurb in the imtransform help why pure translation...
  • Loren Shure: If you look towards the end of the fftfilt program, you will see that there’s a check to see if...
  • Steve: Sonja—My imwritesize submission on the MATLAB Central File Exchange might be helpful. It was posted...
  • Steve: Grant—Sorry, but it won’t be for R2010a. That development deadline has already passed.
  • Sonja: My publisher is wanting images for a new book to be 300 dpi. Only 5 of the 19 images are 300, the rest are...

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