You are hereFeed aggregator

# Feed aggregator

• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.
• warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/thor/Sites/modules/aggregator/aggregator.pages.inc on line 259.

## Talking Walkaway at Melbourne’s Wheeler Centre

Cory Doctorow - 2018, March 27 - 06:49

The Wheeler Centre just posted the audio (MP3) of my event there with C.S. Pacat, as part of my Australia/NZ book tour. It’s a great interview, and we had a lively Q&A!

Categories: Blogs

## Podcast: The Man Who Sold the Moon, Part 07

Cory Doctorow - 2018, March 26 - 07:46

Here’s part seven of my reading (MP3) (part six, part five, part four, part three, part two, part one) of The Man Who Sold the Moon, my award-winning novella first published in 2015’s Hieroglyph: Stories and Visions for a Better Future, edited by Ed Finn and Kathryn Cramer. It’s my Burning Man/maker/first days of a better nation story and was a kind of practice run for my 2017 novel Walkaway.

Categories: Blogs

## Podcast: The Man Who Sold the Moon, Part 06 [FIXED]

Cory Doctorow - 2018, March 18 - 07:25

Here’s part six of my reading (MP3) (part five, part four, part three, part two, part one) of The Man Who Sold the Moon, my award-winning novella first published in 2015’s Hieroglyph: Stories and Visions for a Better Future, edited by Ed Finn and Kathryn Cramer. It’s my Burning Man/maker/first days of a better nation story and was a kind of practice run for my 2017 novel Walkaway.

Categories: Blogs

## Podcast: The Man Who Sold the Moon, Part 05 [FIXED]

Cory Doctorow - 2018, March 18 - 07:23

Here’s part five of my reading (MP3) (part four, part three, part two, part one) of The Man Who Sold the Moon, my award-winning novella first published in 2015’s Hieroglyph: Stories and Visions for a Better Future, edited by Ed Finn and Kathryn Cramer. It’s my Burning Man/maker/first days of a better nation story and was a kind of practice run for my 2017 novel Walkaway.

Categories: Blogs

## What is the Shape of a Pixel?

Matlab Image processing blog - 2018, March 16 - 09:19

Contents

The Shape of a Pixel

What is the shape of a pixel? At various times, I have a pixel as a square (often), a point (sometimes), or a rectangle (occasionally). I recall back in grad school doing some homework where we were treating pixels as hexagons.

As I haved worked through the last few posts on computing Feret diameters, though, I have started to entertain the possible usefulness of considering pixels to be circles. (See 29-Sep-2017, 24-Oct-2017, and 20-Feb-2018.) Let me try to explain why.

Here's a binary image with a single foreground blob (or "object," or "connected component.")

bw = imread('Martha''s Vineyard (30x20).png'); imshow(bw)

Most of the time, we think of image pixels as being squares with unit area.

pixelgrid

We can use find to get the $x$- and $y$-coordinates of the pixel centers, and then we can use convhull to find their convex hull. As an optimization that I think will often reduce execution time and memory, I'm going to preprocess the input binary image here by calling bwperim. I'm not going to show that step everywhere in this example, though.

[y,x] = find(bwperim(bw)); hold on plot(x,y,'.') hold off title('Pixel centers') h = convhull(x,y); x_hull = x(h); y_hull = y(h); hold on hull_line = plot(x_hull,y_hull,'r*','MarkerSize',12); hold off title('Pixel centers and convex hull vertices')

Notice that there are some chains of three or more colinear convex hull vertices.

xlim([21.5 32.5]) ylim([9.5 15.5]) title('Colinear convex hull vertices')

In some of the other processing steps related to Feret diameter measurements, colinear convex hull vertices can cause problems. We can eliminate these vertices directly in the call to convhull using the 'Simplify' parameter.

h = convhull(x,y,'Simplify',true); x_hull = x(h); y_hull = y(h); delete(hull_line); hold on plot(x_hull,y_hull,'r*','MarkerSize',12) hold off title('Colinear hull vertices removed') imshow(bw) hold on plot(x_hull,y_hull,'r-*','LineWidth',2,'MarkerSize',12) hold off title('A Blob''s Convex Hull and Its Vertices')

Notice, though, that there are white bits showing outside the red convex hull polygon. That's because we are only using the pixel centers.

Weaknesses of Using the Pixel Centers

Consider a simpler binary object, one that has only one row.

bw2 = false(5,15); bw2(3,5:10) = true; imshow(bw2) pixelgrid [y,x] = find(bw2);

The function convhull doesn't even work on colinear points.

try hull = convhull(x,y,'Simplify',true); catch e fprintf('Error message from convhull: "%s"\n', e.message); end Error message from convhull: "Error computing the convex hull. The points may be collinear."

But even if it did return an answer, the answer would be a degenerate polygon with length 5 (even though the number of foreground pixels is 6) and zero area.

hold on plot(x,y,'r-*','MarkerSize',12,'LineWidth',2) hold off title('Degenerate convex hull polygon')

We can solve this degeneracy problem by using square pixels.

Square Pixels

In the computation of the convex hull above, we treated each pixel as a point. We can, instead, treat each pixel as a square by computing the convex hull of all the corners of every pixel. Here's one way to perform that computation.

offsets = [ ... 0.5 -0.5 0.5 0.5 -0.5 -0.5 -0.5 0.5 ]'; offsets = reshape(offsets,1,2,[]); P = [x y]; Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); h = convhull(S,'Simplify',true); x_hull = S(h,1); y_hull = S(h,2); imshow(bw2) pixelgrid hold on plot(x_hull,y_hull,'r-*','MarkerSize',12,'LineWidth',2) hold off title('Convex hull of square pixels')

This result looks good at first glance. However, it loses some of its appeal when you consider the implications for computing the maximum Feret diameter.

points = [x_hull y_hull]; [d,end_points] = maxFeretDiameter(points,antipodalPairs(points)) hold on plot(end_points(:,1),end_points(:,2),'k','LineWidth',3) hold off title('The maximum Feret diameter is not horizontal') d = 6.0828 end_points = 10.5000 2.5000 4.5000 3.5000

The maximum Feret distance of this horizontal segment of is 6.0828 ($\sqrt{37}$) instead of 6, and the corresponding orientation in degrees is:

atan2d(1,6) ans = 9.4623

Another worthy attempt is to use diamond pixels.

Diamond Pixels

Instead of using the four corners of each pixel, let's try using the middle of each pixel edge. Once we define the offsets, the code is exactly the same as for square pixels.

offsets = [ ... 0.5 0.0 0.0 0.5 -0.5 0.0 0.0 -0.5 ]'; offsets = reshape(offsets,1,2,[]); P = [x y]; Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); h = convhull(S,'Simplify',true); x_hull = S(h,1); y_hull = S(h,2); imshow(bw2) pixelgrid hold on plot(x_hull,y_hull,'r-*','MarkerSize',12,'LineWidth',2) hold off title('Convex hull of diamond pixels')

Now the max Feret diameter result looks better for the horizontal row of pixels.

points = [x_hull y_hull]; [d,end_points] = maxFeretDiameter(points,antipodalPairs(points)) hold on plot(end_points(:,1),end_points(:,2),'k','LineWidth',3) hold off d = 6 end_points = 10.5000 3.0000 4.5000 3.0000

Hold on, though. Consider a square blob.

bw3 = false(9,9); bw3(3:7,3:7) = true; imshow(bw3) pixelgrid [y,x] = find(bw3); P = [x y]; Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); h = convhull(S,'Simplify',true); x_hull = S(h,1); y_hull = S(h,2); hold on plot(x_hull,y_hull,'r-*','MarkerSize',12,'LineWidth',2) points = [x_hull y_hull]; [d,end_points] = maxFeretDiameter(points,antipodalPairs(points)) plot(end_points(:,1),end_points(:,2),'k','LineWidth',3) hold off title('The max Feret diameter is not at 45 degrees') d = 6.4031 end_points = 7.5000 3.0000 2.5000 7.0000

We'd like to see the max Feret diameter oriented at 45 degrees, and clearly we don't.

Circular Pixels

OK, I'm going to make one more attempt. I'm going to treat each pixel as approximately a circle. I'm going to approximate a circle using 24 points that are spaced at 15-degree intervals along the circumference.

thetad = 0:15:345; offsets = 0.5*[cosd(thetad) ; sind(thetad)]; offsets = reshape(offsets,1,2,[]); Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); h = convhull(S,'Simplify',true); x_hull = S(h,1); y_hull = S(h,2); imshow(bw3) pixelgrid hold on plot(x_hull,y_hull,'r-*','MarkerSize',12,'LineWidth',2) points = [x_hull y_hull]; [d,end_points] = maxFeretDiameter(points,antipodalPairs(points)) plot(end_points(:,1),end_points(:,2),'k','LineWidth',3) axis on hold off d = 6.6569 end_points = 7.3536 7.3536 2.6464 2.6464

Now the max Feret diameter orientation is what we would naturally expect, which is $\pm 45^{\circ}$. The orientation would also be as expected for a horizontal or vertical segment of pixels.

Still, a circular approximation might not always give exactly what a user might expect. Let's go back to the Martha's Vinyard blob that I started with. I wrote a function called pixelHull that can compute the convex hull of binary image pixels in a variety of different ways. The call pixelHull(bw,24) computes the pixel hull using a 24-point circle approximation.

Here's the maximum Feret diameter using that approximation.

imshow(bw) V = pixelHull(bw,24); hold on plot(V(:,1),V(:,2),'r-','LineWidth',2,'MarkerSize',12) [d,end_points] = maxFeretDiameter(V,antipodalPairs(V)); plot(end_points(:,1),end_points(:,2),'m','LineWidth',3) axis on pixelgrid hold off

I think many people might expect the maximum Feret diameter to go corner-to-corner in this case, but it doesn't exactly do that.

xlim([22.07 31.92]) ylim([8.63 15.20])

You have to use square pixels to get corner-to-corner.

imshow(bw) V = pixelHull(bw,'square'); hold on plot(V(:,1),V(:,2),'r-','LineWidth',2,'MarkerSize',12) [d,end_points] = maxFeretDiameter(V,antipodalPairs(V)); plot(end_points(:,1),end_points(:,2),'m','LineWidth',3) axis on pixelgrid hold off xlim([22.07 31.92]) ylim([8.63 15.20])

After all this, I'm still not completely certain which shape assumption will generally work best. My only firm conclusion is that the point approximation is the worst choice. The degeneracies associated with point pixels are just too troublesome.

If you have an opinion, please share it in the comments. (Note: A comment that says, "Steve, you're totally overthinking this" would be totally legit.)

The rest of the post contains functions used by the code above.

function V = pixelHull(P,type) if nargin < 2 type = 24; end if islogical(P) P = bwperim(P); [i,j] = find(P); P = [j i]; end if strcmp(type,'square') offsets = [ ... 0.5 -0.5 0.5 0.5 -0.5 0.5 -0.5 -0.5 ]; elseif strcmp(type,'diamond') offsets = [ ... 0.5 0 0 0.5 -0.5 0 0 -0.5 ]; else % type is number of angles for sampling a circle of diameter 1. thetad = linspace(0,360,type+1)'; thetad(end) = []; offsets = 0.5*[cosd(thetad) sind(thetad)]; end offsets = offsets'; offsets = reshape(offsets,1,2,[]); Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); k = convhull(S,'Simplify',true); V = S(k,:); end \n'); d.write(code_string); // Add copyright line at the bottom if specified. if (copyright.length > 0) { d.writeln(''); d.writeln('%%'); if (copyright.length > 0) { d.writeln('% _' + copyright + '_'); } } d.write('\n'); d.title = title + ' (MATLAB code)'; d.close(); } -->

Get the MATLAB code (requires JavaScript)

Published with MATLAB® R2017b

, , and .) Let me try to explain why. % % Here's a binary image with a single foreground blob (or "object," or "connected % component.") bw = imread('Martha''s Vineyard (30x20).png'); imshow(bw) %% % Most of the time, we think of image pixels as being squares with unit % area. %% pixelgrid %% % We can use |find| to get the $x$- and $y$-coordinates of the pixel centers, % and then we can use |convhull| to find their convex hull. As an optimization % that I think will often reduce execution time and memory, I'm going to preprocess % the input binary image here by calling |bwperim|. I'm not going to show that % step everywhere in this example, though. %% [y,x] = find(bwperim(bw)); hold on plot(x,y,'.') hold off title('Pixel centers') h = convhull(x,y); x_hull = x(h); y_hull = y(h); hold on hull_line = plot(x_hull,y_hull,'r*','MarkerSize',12); hold off title('Pixel centers and convex hull vertices') %% % Notice that there are some chains of three or more colinear convex hull % vertices. %% xlim([21.5 32.5]) ylim([9.5 15.5]) title('Colinear convex hull vertices') %% % In some of the other processing steps related to Feret diameter measurements, % colinear convex hull vertices can cause problems. We can eliminate these vertices % directly in the call to |convhull| using the |'Simplify'| parameter. %% h = convhull(x,y,'Simplify',true); x_hull = x(h); y_hull = y(h); delete(hull_line); hold on plot(x_hull,y_hull,'r*','MarkerSize',12) hold off title('Colinear hull vertices removed') %% imshow(bw) hold on plot(x_hull,y_hull,'r-*','LineWidth',2,'MarkerSize',12) hold off title('A Blob''s Convex Hull and Its Vertices') %% % Notice, though, that there are white bits showing outside the red convex % hull polygon. That's because we are only using the *pixel centers*. %% Weaknesses of Using the Pixel Centers % Consider a simpler binary object, one that has only one row. %% bw2 = false(5,15); bw2(3,5:10) = true; imshow(bw2) pixelgrid [y,x] = find(bw2); %% % The function |convhull| doesn't even work on colinear points. try hull = convhull(x,y,'Simplify',true); catch e fprintf('Error message from convhull: "%s"\n', e.message); end %% % But even if it did return an answer, the answer would be a degenerate % polygon with length 5 (even though the number of foreground pixels is 6) and % zero area. hold on plot(x,y,'r-*','MarkerSize',12,'LineWidth',2) hold off title('Degenerate convex hull polygon') %% % We can solve this degeneracy problem by using *square pixels*. %% Square Pixels % In the computation of the convex hull above, we treated each pixel as a *point*. % We can, instead, treat each pixel as a *square* by computing the convex hull % of all the *corners* of every pixel. Here's one way to perform that computation. offsets = [ ... 0.5 -0.5 0.5 0.5 -0.5 -0.5 -0.5 0.5 ]'; offsets = reshape(offsets,1,2,[]); P = [x y]; Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); h = convhull(S,'Simplify',true); x_hull = S(h,1); y_hull = S(h,2); %% imshow(bw2) pixelgrid hold on plot(x_hull,y_hull,'r-*','MarkerSize',12,'LineWidth',2) hold off title('Convex hull of square pixels') %% % This result looks good at first glance. However, it loses some of its % appeal when you consider the implications for computing the maximum Feret diameter. %% points = [x_hull y_hull]; [d,end_points] = maxFeretDiameter(points,antipodalPairs(points)) hold on plot(end_points(:,1),end_points(:,2),'k','LineWidth',3) hold off title('The maximum Feret diameter is not horizontal') %% % The maximum Feret distance of this horizontal segment of is 6.0828 ($\sqrt{37}$) % instead of 6, and the corresponding orientation in degrees is: atan2d(1,6) %% % instead of 0. % % Another worthy attempt is to use *diamond* pixels. %% Diamond Pixels % Instead of using the four corners of each pixel, let's try using the middle % of each pixel edge. Once we define the offsets, the code is exactly the same % as for square pixels. offsets = [ ... 0.5 0.0 0.0 0.5 -0.5 0.0 0.0 -0.5 ]'; offsets = reshape(offsets,1,2,[]); P = [x y]; Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); h = convhull(S,'Simplify',true); x_hull = S(h,1); y_hull = S(h,2); %% imshow(bw2) pixelgrid hold on plot(x_hull,y_hull,'r-*','MarkerSize',12,'LineWidth',2) hold off title('Convex hull of diamond pixels') %% % Now the max Feret diameter result looks better for the horizontal row % of pixels. %% points = [x_hull y_hull]; [d,end_points] = maxFeretDiameter(points,antipodalPairs(points)) hold on plot(end_points(:,1),end_points(:,2),'k','LineWidth',3) hold off %% % Hold on, though. Consider a square blob. bw3 = false(9,9); bw3(3:7,3:7) = true; imshow(bw3) pixelgrid [y,x] = find(bw3); P = [x y]; Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); h = convhull(S,'Simplify',true); x_hull = S(h,1); y_hull = S(h,2); %% hold on plot(x_hull,y_hull,'r-*','MarkerSize',12,'LineWidth',2) points = [x_hull y_hull]; [d,end_points] = maxFeretDiameter(points,antipodalPairs(points)) plot(end_points(:,1),end_points(:,2),'k','LineWidth',3) hold off title('The max Feret diameter is not at 45 degrees') %% % We'd like to see the max Feret diameter oriented at 45 degrees, and clearly % we don't. %% Circular Pixels % OK, I'm going to make one more attempt. I'm going to treat each pixel as % *approximately* a *circle*. I'm going to approximate a circle using 24 points % that are spaced at 15-degree intervals along the circumference. thetad = 0:15:345; offsets = 0.5*[cosd(thetad) ; sind(thetad)]; offsets = reshape(offsets,1,2,[]); Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); h = convhull(S,'Simplify',true); x_hull = S(h,1); y_hull = S(h,2); imshow(bw3) pixelgrid hold on plot(x_hull,y_hull,'r-*','MarkerSize',12,'LineWidth',2) points = [x_hull y_hull]; [d,end_points] = maxFeretDiameter(points,antipodalPairs(points)) plot(end_points(:,1),end_points(:,2),'k','LineWidth',3) axis on hold off %% % Now the max Feret diameter orientation is what we would naturally expect, % which is $\pm 45^{\circ}$. The orientation would also be as expected for a horizontal % or vertical segment of pixels. % % Still, a circular approximation might not always give exactly what a user % might expect. Let's go back to the Martha's Vinyard blob that I started with. % I wrote a function called |pixelHull| that can compute the convex hull of binary % image pixels in a variety of different ways. The call |pixelHull(bw,24)| computes % the pixel hull using a 24-point circle approximation. % % Here's the maximum Feret diameter using that approximation. imshow(bw) V = pixelHull(bw,24); hold on plot(V(:,1),V(:,2),'r-','LineWidth',2,'MarkerSize',12) [d,end_points] = maxFeretDiameter(V,antipodalPairs(V)); plot(end_points(:,1),end_points(:,2),'m','LineWidth',3) axis on pixelgrid hold off %% % I think many people might expect the maximum Feret diameter to go corner-to-corner % in this case, but it doesn't exactly do that. %% xlim([22.07 31.92]) ylim([8.63 15.20]) %% % You have to use square pixels to get corner-to-corner. %% imshow(bw) V = pixelHull(bw,'square'); hold on plot(V(:,1),V(:,2),'r-','LineWidth',2,'MarkerSize',12) [d,end_points] = maxFeretDiameter(V,antipodalPairs(V)); plot(end_points(:,1),end_points(:,2),'m','LineWidth',3) axis on pixelgrid hold off %% xlim([22.07 31.92]) ylim([8.63 15.20]) %% % After all this, I'm still not completely certain which shape assumption % will generally work best. My only firm conclusion is that the point approximation % is the worst choice. The degeneracies associated with point pixels are just % too troublesome. % % If you have an opinion, please share it in the comments. (Note: A comment % that says, "Steve, you're totally overthinking this" would be totally legit.) % % _The rest of the post contains functions used by the code above._ %% function V = pixelHull(P,type) if nargin < 2 type = 24; end if islogical(P) P = bwperim(P); [i,j] = find(P); P = [j i]; end if strcmp(type,'square') offsets = [ ... 0.5 -0.5 0.5 0.5 -0.5 0.5 -0.5 -0.5 ]; elseif strcmp(type,'diamond') offsets = [ ... 0.5 0 0 0.5 -0.5 0 0 -0.5 ]; else % type is number of angles for sampling a circle of diameter 1. thetad = linspace(0,360,type+1)'; thetad(end) = []; offsets = 0.5*[cosd(thetad) sind(thetad)]; end offsets = offsets'; offsets = reshape(offsets,1,2,[]); Q = P + offsets; R = permute(Q,[1 3 2]); S = reshape(R,[],2); k = convhull(S,'Simplify',true); V = S(k,:); end ##### SOURCE END ##### f024816cb729487aaace442476b0b27b -->

Categories: Blogs

## Hugo nominations close tomorrow!

Cory Doctorow - 2018, March 15 - 09:59

If you attended either of the past two World Science Fiction Conventions or are registered for the next one in San Jose, California, you’re eligible to nominate for the Hugo Awards, which you can do here — you’ve only got until midnight tomorrow!

The 2017 Locus Recommended Reading List is a great place to start if you’re looking to refresh your memory about the sf/f you enjoyed last year.

May I humbly remind you that my novel Walkaway is eligible in the Best Novel category?

(via Scalzi)

Categories: Blogs

## Classroom materials for Little Brother from Mary Kraus

Cory Doctorow - 2018, March 8 - 12:02

Mary Kraus — who created a key to page-numbers in the Little Brother audiobook for students with reading disabilities — continues to create great classroom materials for Little Brother: Who’s Who in “Little Brother” is a Quizlet that teaches about the famous people mentioned in the book, from Alan Turing to Rosa Luxembourg; while the Acronym Challenge asks students to unpack acronyms like DHS, NPR, IM, DNS, and ACLU.

Categories: Blogs

Cory Doctorow - 2018, March 6 - 16:26

I’ve just finished a wonderful time at the Adelaide Festival and now I’m headed to the last stop on the Australia/New Zealand tour for Walkaway: Wellington!

I’m doing a pair of events at Writers & Readers Week at the New Zealand Festival; followed by a special one-day NetHui on copyright and then a luncheon seminar for the Privacy Commissioner on “machine learning, big data and being less wrong.”

It starts on the 9th of March and finishes on the 13th, and I really hope I see you there! Thanks to everyone who’s come out in Perth, Sydney, Melbourne and Adelaide; you’ve truly made this a tour to remember.

Categories: Blogs

## How to be better at being pissed off at Big Tech

Cory Doctorow - 2018, March 5 - 15:59

My latest Locus column, “Let’s Get Better at Demanding Better from Tech,” looks at how science fiction can make us better critics of technology by imagining how tech could be used in difference social and economic contexts than the one we live in today.

The “pro-tech” side’s argument is some variation on, “You can’t get the social benefits of Facebook without letting us spy on you and manipulate you — if you want to stay in touch with your friends, that’s the price of admission.” All too often, the “anti-tech” side takes this premise at face value: “Since we can’t hang out with our friends online without being spied on and manipulated, you need to stop wanting to hang out with your friends online.”

But the science fiction version of this goes, “What kinds of systems could we build if we wanted to hang out with our friends without being spied on and manipulated — and what kinds of political, regulatory and technological interventions would make those systems easier to build?”

A critique of technology that focuses on its market conditions, rather than its code, yields up some interesting alternate narratives. It has become fashionable, for example, to say that advertising was the original sin of online publication. Once the norm emerged that creative work would be free and paid for through attention – that is, by showing ads – the wheels were set in motion, leading to clickbait, political polarization, and invasive, surveillant networks: “If you’re not paying for the product, you’re the product.”

But if we understand the contours of the advertising marketplace as being driven by market conditions, not “attention economics,” a different story emerges. Market conditions have driven incredible consolidation in every sector of the economy, meaning that fewer and fewer advertisers call the shots, and meaning that more and more of the money flows through fewer and fewer payment processors. Compound that with lax anti-trust enforcement, and you have companies that are poised to put pressure on publishers and control who sees which information.

In 2018, companies from John Deere to GM to Johnson & Johnson use digital locks and abusive license agreements to force you to submit to surveillance and control how you use their products. It’s true that if you don’t pay for the product, you’re the product – but if you’re a farmer who’s just shelled out \$500,000 for a new tractor, you’re still the product.

The “original sin of advertising” story says that if only microtransactions had been technologically viable and commercially attractive, we could have had an attention-respecting, artist-compensating online world, but in a world of mass inequality, financializing culture and discourse means excluding huge swaths of the population from the modern public sphere. If the Supreme Court’s Citizens United decision has you convinced that money has had a corrupting influence on who gets to speak, imagine how corrupting the situation would be if you also had to pay to listen.

Let’s Get Better at Demanding Better from Tech [Cory Doctorow/Locus]

Categories: Blogs

## A key to page-numbers in the Little Brother audiobook

Cory Doctorow - 2018, March 2 - 18:34

Mary Kraus teaches my novel Little Brother to health science interns learning about cybersecurity; to help a student who has a print disability, Mary created a key that maps the MP3 files in the audiobook to the Tor paperback edition. She was kind enough to make her doc public to help other people move easily from the audiobook to the print edition — thanks, Mary!

Categories: Blogs

## I’m coming to the Adelaide Festival this weekend (and then to Wellington, NZ!)

Cory Doctorow - 2018, March 2 - 15:30

I’m on the last two cities in my Australia/NZ tour for my novel Walkaway: today, I’m flying to Adelaide for the Adelaide Festival, where I’m appearing in several program items: Breakfast with Papers on Sunday at 8AM; a book signing on Monday at 10AM in Dymocks at Rundle Mall; “Dust Devils,” a panel followed by a signing on Monday at 5PM on the West Stage at Pioneer Women’s Memorial Garden; and “Craphound,” a panel/signing on Tuesday at 5PM on the East Stage at Pioneer Women’s Memorial Garden.

After Adelaide, I’m off to Wellington for Writers and Readers Week and then the NetHui one-day copyright event.

I’ve had a fantastic time in Perth, Melbourne and Sydney and it’s been such a treat to meet so many of you — I’m looking so forward to these last two stops!

Categories: Blogs

## Hey, Sydney! I’m coming to see you tonight (then Adelaide and Wellington!)

Cory Doctorow - 2018, February 28 - 13:53

I’m just about to go to the airport to fly to Sydney for tonight’s event, What should we do about Democracy?

It’s part of the Australia/New Zealand tour for Walkaway, and from Sydney, I’m moving on to the Adelaide Festival and then to Wellington for Writers and Readers Week and the NetHui one-day event on copyright.

It feels like democracy is under siege, even in rich, peaceful countries like Australia that have escaped financial shocks and civil strife. Populist impulses have been unleashed in the UK and USA. There is a record lack of trust in the institutions of politics and government, exacerbated by the ways in which social media and digital technology can spread ‘fake news’ and are being harnessed by foreign powers to meddle in politics. Important issues that citizens care about, like climate change, are sidelined by professional politicians, enhancing the appeal of outsider figures. Do these problems add up to the failure of democracy? Are Brexit and Trump outliers, or the new normal? Join a lively panel of experts and commentators explore some big questions about the future of democracy, and think more clearly about what we ought to do.

Speakers Cory Doctorow, A.C. Grayling, Rebecca Huntley and Lenore Taylor

Chair Jeremy Moss

Categories: Blogs

## My short story about better cities, where networks give us the freedom to schedule our lives to avoid heat-waves and traffic jams

Cory Doctorow - 2018, February 28 - 13:47

I was lucky enough to be invited to submit a piece to Ian Bogost’s Atlantic series on the future of cities (previously: James Bridle, Bruce Sterling, Molly Sauter, Adam Greenfield); I told Ian I wanted to build on my 2017 Locus column about using networks to allow us to coordinate our work and play in a way that maximized our freedom, so that we could work outdoors on nice days, or commute when the traffic was light, or just throw an impromptu block party when the neighborhood needed a break.

The story is out today, with a gorgeous illustration by Molly Crabapple; the Atlantic called it “The City of Coordinated Leisure,” but in my heart it will always be “Coase’s Day Off: a microeconomics of coordinated leisure.”

There had been some block parties on Lima Street when Arturo had been too small to remember them, but then there had been a long stretch of unreasonably seasonable weather and no one had tried it, not until the year before, on April 18, a Thursday after a succession of days that vied to top each other for inhumane conditions, the weather app on the hallway wall showing 112 degrees before breakfast.

Mr. Papazian was the block captain for that party, and the first they’d known of it was when Arturo’s dad called out to his mom that Papazian had messaged them about a block party, and there was something funny in Dad’s tone, a weird mix of it’s so crazy and let’s do it.

That had been a day to remember, and Arturo had remembered, and watched the temperature.

The City of Coordinated Leisure [Cory Doctorow/The Atlantic]

Categories: Blogs

## Podcast: The Man Who Sold the Moon, Part 05

Cory Doctorow - 2018, February 26 - 05:01

Here’s part five of my reading (MP3) (part four, part three, part two, part one) of The Man Who Sold the Moon, my award-winning novella first published in 2015’s Hieroglyph: Stories and Visions for a Better Future, edited by Ed Finn and Kathryn Cramer. It’s my Burning Man/maker/first days of a better nation story and was a kind of practice run for my 2017 novel Walkaway.

Categories: Blogs

## Minimum Feret Diameter

Matlab Image processing blog - 2018, February 20 - 16:30

Last time (if you can remember that long ago), I talked about how to find the maximum Feret diameter of a shape. The Feret diameter, sometimes called the caliper diameter, is illustrated by the diagram below. In a virtual sense, place the object to be measured inside the jaws of a caliper, with the caliper oriented at a specified angle. Close the jaws tightly on the object while maintaining that angle. The distance between the jaws is the Feret diameter at that angle.

In the last post, I demonstrated how to find all the antipodal vertex pairs of a shape, which is a useful step in finding the maximum Feret diameters of a shape.

Here is a convex shape with all of the antipodal vertex pairs shown.

It turns out that the minimum Feret diameter can also be found by looking at these antipodal pairs. Furthermore, it happens that the minimum-distance Feret calipers touch the shape at three of these vertices. I'm not going to try to prove that here, but let me illustrate it with a couple of diagrams.

Here is a shape with a couple of "caliper lines" drawn at -30 degrees. (Note that the y-axis is reversed; that's why the angle is negative.)

hull = [ 2.5000 5.5000 3.5000 4.5000 6.5000 2.5000 9.5000 1.5000 10.5000 1.5000 10.5000 3.5000 9.5000 5.5000 5.5000 7.5000 2.5000 7.5000 2.5000 5.5000 ]; plot(hull(:,1),hull(:,2),'r','LineWidth',2) axis equal axis ij axis([0 15 0 10]) hold on plot(hull(:,1),hull(:,2),'r*') [x1,y1] = fullLine(gca,[9.5 5.5],-30); [x2,y2] = fullLine(gca,[6.5 2.5],-30); caliper_lines(1) = plot(x1,y1,'k'); caliper_lines(2) = plot(x2,y2,'k'); hold off

You can always rotate these two caliper lines, by the same angle, until at least one of them touches the next antipodal vertex. When you do that rotation, the distance between the lines shrinks.

set(caliper_lines,'Color',[0.8 0.8 0.8]); [x3,y3] = fullLine(gca,[9.5 5.5],thetad); [x4,y4] = fullLine(gca,[6.5 2.5],thetad); hold on plot(x3,y3,'k') plot(x4,y4,'k') hold off

The Feret diameter at that rotated angle, then, is the height of the triangle formed by the three vertices, with the base of the triangle defined by the two vertices touched by the same caliper line.

delete(caliper_lines); hold on plot([5.5 9.5 6.5 5.5],[7.5 5.5 2.5 7.5],'Color','b','LineWidth',2) hold off

The function minFeretDiameter, which appears at the end of this post, simply walks around the set of adjacent-vertex triangles formed from antipodal vertex pairs, looking for the triangle with the minimum height.

Let's look for the minimum Feret diameter in a slightly more interesting shape.

load shape plot(bx,by,'LineWidth',1) axis equal axis ij axis([0 650 0 650])

Find the convex hull, making sure to simplify it, and then find the antipodal pairs. (I showed the function antipodalPairs in my previous post.)

hull = convhull(bx,by,'Simplify',true); S = [bx(hull) by(hull)]; pairs = antipodalPairs(S);

Now we can call minFeretDiameter.

[d,tri_points] = minFeretDiameter(S,pairs) d = 318.7890 tri_points = 613 211 459 443 239 198

Superimpose the minimum-height triangle that minFeretDiameter found.

tri_points = [tri_points; tri_points(1,:)]; hold on plot(tri_points(:,1),tri_points(:,2),'LineWidth',2) hold off

Now calculate the caliper angle so that we can visualize the parallel lines of support corresponding to the minimum diameter.

dx = tri_points(2,1) - tri_points(1,1); dy = tri_points(2,2) - tri_points(1,2); angle = atan2d(dy,dx); [x5,y5] = fullLine(gca,tri_points(1,:),angle); [x6,y6] = fullLine(gca,tri_points(3,:),angle); hold on plot(x5,y5,'k') plot(x6,y6,'k') hold off

For next time, I'm tentatively planning to put together some of these concepts and use them to compute the minimum bounding box for an object.

function [x,y] = fullLine(ax,point,angle_degrees) % Steve Eddins limits = axis(ax); width = abs(limits(2) - limits(1)); height = abs(limits(4) - limits(3)); d = 2*hypot(width,height); x1 = point(1) - d*cosd(angle_degrees); x2 = point(1) + d*cosd(angle_degrees); y1 = point(2) - d*sind(angle_degrees); y2 = point(2) + d*sind(angle_degrees); x = [x1 x2]; y = [y1 y2]; end function [d,triangle_points] = minFeretDiameter(V,antipodal_pairs) % Steve Eddins if nargin < 2 antipodal_pairs = antipodalPairs(V); end n = size(antipodal_pairs,1); p = antipodal_pairs(:,1); q = antipodal_pairs(:,2); d = Inf; triangle_points = []; for k = 1:n if k == n k1 = 1; else k1 = k+1; end pt1 = []; pt2 = []; pt3 = []; if (p(k) ~= p(k1)) && (q(k) == q(k1)) pt1 = V(p(k),:); pt2 = V(p(k1),:); pt3 = V(q(k),:); elseif (p(k) == p(k1)) && (q(k) ~= q(k1)) pt1 = V(q(k),:); pt2 = V(q(k1),:); pt3 = V(p(k),:); end if ~isempty(pt1) % Points pt1, pt2, and pt3 form a possible minimum Feret diameter. % Points pt1 and pt2 form an edge parallel to caliper direction. % The Feret diameter orthogonal to the pt1-pt2 edge is the height % of the triangle with base pt1-pt2. d_k = triangleHeight(pt1,pt2,pt3); if d_k < d d = d_k; triangle_points = [pt1; pt2; pt3]; end end end end \n'); d.write(code_string); // Add copyright line at the bottom if specified. if (copyright.length > 0) { d.writeln(''); d.writeln('%%'); if (copyright.length > 0) { d.writeln('% _' + copyright + '_'); } } d.write('\n'); d.title = title + ' (MATLAB code)'; d.close(); } -->

Get the MATLAB code (requires JavaScript)

Published with MATLAB® R2017b

(if you can remember that long ago), I talked about how to % find the maximum _Feret diameter_ of a shape. The Feret diameter, % sometimes called the _caliper diameter_, is illustrated by the diagram % below. In a virtual sense, place the object to be measured inside the % jaws of a caliper, with the caliper oriented at a specified angle. Close % the jaws tightly on the object while maintaining that angle. The distance % between the jaws is the Feret diameter at that angle. % % <> % % In the last post, I demonstrated how to find all the _antipodal_ vertex % pairs of a shape, which is a useful step in finding the maximum Feret % diameters of a shape. % % Here is a convex shape with all of the antipodal vertex pairs shown. % % <> % % It turns out that the *minimum* Feret diameter can also be found by % looking at these antipodal pairs. Furthermore, it happens that the % minimum-distance Feret calipers touch the shape at *three* of these % vertices. I'm not going to try to prove that here, but let me illustrate % it with a couple of diagrams. % % Here is a shape with a couple of "caliper lines" drawn at -30 degrees. % (Note that the y-axis is reversed; that's why the angle is negative.) %% hull = [ 2.5000 5.5000 3.5000 4.5000 6.5000 2.5000 9.5000 1.5000 10.5000 1.5000 10.5000 3.5000 9.5000 5.5000 5.5000 7.5000 2.5000 7.5000 2.5000 5.5000 ]; plot(hull(:,1),hull(:,2),'r','LineWidth',2) axis equal axis ij axis([0 15 0 10]) hold on plot(hull(:,1),hull(:,2),'r*') [x1,y1] = fullLine(gca,[9.5 5.5],-30); [x2,y2] = fullLine(gca,[6.5 2.5],-30); caliper_lines(1) = plot(x1,y1,'k'); caliper_lines(2) = plot(x2,y2,'k'); hold off %% % You can always rotate these two caliper lines, by the same angle, until % at least one of them touches the *next* antipodal vertex. When you do % that rotation, the distance between the lines shrinks. set(caliper_lines,'Color',[0.8 0.8 0.8]); [x3,y3] = fullLine(gca,[9.5 5.5],thetad); [x4,y4] = fullLine(gca,[6.5 2.5],thetad); hold on plot(x3,y3,'k') plot(x4,y4,'k') hold off %% % The Feret diameter at that rotated angle, then, is the height of the % triangle formed by the three vertices, with the base of the triangle % defined by the two vertices touched by the same caliper line. delete(caliper_lines); hold on plot([5.5 9.5 6.5 5.5],[7.5 5.5 2.5 7.5],'Color','b','LineWidth',2) hold off %% % The function |minFeretDiameter|, which appears at the end of this post, % simply walks around the set of adjacent-vertex triangles formed from % antipodal vertex pairs, looking for the triangle with the minimum height. % % Let's look for the minimum Feret diameter in a slightly more interesting % shape. load shape plot(bx,by,'LineWidth',1) axis equal axis ij axis([0 650 0 650]) %% % Find the convex hull, making sure to simplify it, and then find the % antipodal pairs. (I showed the function |antipodalPairs| in my previous % post.) hull = convhull(bx,by,'Simplify',true); S = [bx(hull) by(hull)]; pairs = antipodalPairs(S); %% % Now we can call |minFeretDiameter|. [d,tri_points] = minFeretDiameter(S,pairs) %% % Superimpose the minimum-height triangle that |minFeretDiameter| found. tri_points = [tri_points; tri_points(1,:)]; hold on plot(tri_points(:,1),tri_points(:,2),'LineWidth',2) hold off %% % Now calculate the caliper angle so that we can visualize the parallel % lines of support corresponding to the minimum diameter. dx = tri_points(2,1) - tri_points(1,1); dy = tri_points(2,2) - tri_points(1,2); angle = atan2d(dy,dx); [x5,y5] = fullLine(gca,tri_points(1,:),angle); [x6,y6] = fullLine(gca,tri_points(3,:),angle); %% hold on plot(x5,y5,'k') plot(x6,y6,'k') hold off %% % For next time, I'm tentatively planning to put together some of these % concepts and use them to compute the minimum bounding box for an object. %% function [x,y] = fullLine(ax,point,angle_degrees) % Steve Eddins % Copyright 2017 The MathWorks, Inc. limits = axis(ax); width = abs(limits(2) - limits(1)); height = abs(limits(4) - limits(3)); d = 2*hypot(width,height); x1 = point(1) - d*cosd(angle_degrees); x2 = point(1) + d*cosd(angle_degrees); y1 = point(2) - d*sind(angle_degrees); y2 = point(2) + d*sind(angle_degrees); x = [x1 x2]; y = [y1 y2]; end function [d,triangle_points] = minFeretDiameter(V,antipodal_pairs) % Steve Eddins % Copyright 2017-2018 The MathWorks, Inc. if nargin < 2 antipodal_pairs = antipodalPairs(V); end n = size(antipodal_pairs,1); p = antipodal_pairs(:,1); q = antipodal_pairs(:,2); d = Inf; triangle_points = []; for k = 1:n if k == n k1 = 1; else k1 = k+1; end pt1 = []; pt2 = []; pt3 = []; if (p(k) ~= p(k1)) && (q(k) == q(k1)) pt1 = V(p(k),:); pt2 = V(p(k1),:); pt3 = V(q(k),:); elseif (p(k) == p(k1)) && (q(k) ~= q(k1)) pt1 = V(q(k),:); pt2 = V(q(k1),:); pt3 = V(p(k),:); end if ~isempty(pt1) % Points pt1, pt2, and pt3 form a possible minimum Feret diameter. % Points pt1 and pt2 form an edge parallel to caliper direction. % The Feret diameter orthogonal to the pt1-pt2 edge is the height % of the triangle with base pt1-pt2. d_k = triangleHeight(pt1,pt2,pt3); if d_k < d d = d_k; triangle_points = [pt1; pt2; pt3]; end end end end ##### SOURCE END ##### 83a9c73c5bf74fe384efcbc4e01ca0e4 -->

Categories: Blogs

## Do We Need a New Internet?

Cory Doctorow - 2018, February 15 - 14:35

I was one of the interview subjects on an episode of BBC’s Tomorrow’s World called Do We Need a New Internet? (MP3); it’s a fascinating documentary, including some very thoughtful commentary from Edward Snowden.

Categories: Blogs

## The 2018 Locus Poll is open: choose your favorite science fiction of 2017!

Cory Doctorow - 2018, February 15 - 12:08

Following the publication of its editorial board’s long-list of the best science fiction of 2017, science fiction publishing trade-journal Locus now invites its readers to vote for their favorites in the annual Locus Award. I’m honored to have won this award in the past, and doubly honored to see my novel Walkaway on the short list, and in very excellent company indeed.

While you’re thinking about your Locus List picks, you might also use the list as an aide-memoire in picking your nominees for the Hugo Awards.

Categories: Blogs

## The Man Who Sold the Moon, Part 04 [FIXED]

Cory Doctorow - 2018, February 12 - 22:03

Here’s part four of my reading (MP3) (part three, part two, part one) of The Man Who Sold the Moon, my award-winning novella first published in 2015’s Hieroglyph: Stories and Visions for a Better Future, edited by Ed Finn and Kathryn Cramer. It’s my Burning Man/maker/first days of a better nation story and was a kind of practice run for my 2017 novel Walkaway.

Categories: Blogs

## Hey, Australia and New Zealand, I’m coming to visit you!

Cory Doctorow - 2018, February 11 - 17:05

I’m about to embark on a tour of Australia and New Zealand to support my novel Walkaway, with stops in Perth, Melbourne, Sydney, Adelaide, and Wellington! I really hope you’ll come out and say hello!

Perth: Feb 24-25, Perth Festival

Melbourne: Feb 27: An expansive conversation about the imperfect present and foreseeable future with CS Pascat, St Kilda Town Hall, 19h

Melbourne: Feb 28: How do writers get paid?, Wheeler Centre, 1815h

Sydney: Mar 1: What should we do about democracy?, City Recital Hall, 1930h

Wellington: Mar 9-11: Writers and Readers Week

Wellington: Mar 12: NetHui one-day event on copyright

Categories: Blogs

## HomePod + Android #ForbiddenLove

DVD Jon - 2018, February 9 - 18:29

14 years… It’s hard to believe, but that’s how long it’s been since Apple introduced AirPlay audio streaming (originally called AirTunes) with the release of the first AirPort Express. From Apple’s press release in 2004:

AirTunes is Apple’s breakthrough music networking technology which works seamlessly with iTunes running on either Macs or PCs to let users easily create a wireless music network in their home. iTunes 4.6 automatically detects remote speakers and displays them in a simple pop-up list for the user to select. Once the remote speakers are selected, AirTunes wirelessly streams the iTunes music from the computer to the AirPort Express base station. AirTunes music is encoded to protect it from theft while streaming across the wireless music network and uses Apple’s lossless compression technology to ensure no loss of sound quality.

Shorty after the public release, I reverse engineered the AirTunes protocol and the key used to encrypt the audio stream and released JustePort, an open source AirTunes client.

When Apple in 2010 released iOS 4.2 with support for sending video to the 2nd gen Apple TV, they renamed AirTunes to AirPlay.

Since I co-founded doubleTwist a decade ago, we’ve been at the forefront of restoring digital media interoperability for users trapped in walled gardens. On Android we’ve supported AirPlay since 2011 and we currently support all three major protocols (AirPlay, Chromecast and DLNA). Whether you prefer to store your music locally or in a cloud service like OneDrive or Google Drive, we’ve got you covered with doubleTwist Player and CloudPlayer.

Like many others, we’ve been eagerly awaiting the launch of Apple’s first AirPlay speaker. We got our hands on the HomePod today for some testing and everything works flawlessly. Anything you can play in the doubleTwist apps (local music, cloud music, podcasts, radio) can be streamed to the HomePod.

Currently streaming my lossless music collection stored on Microsoft OneDrive to the Apple HomePod using my Google Pixel XL – sounds amazing!

Categories: Blogs