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.

Superpixel Posterization

Matlab Image processing blog - 2018, May 21 - 14:28

Today's blog post was inspired by an example written by my friend and Image Processing Toolbox developer, Alex Taylor. A few years back, Alex tinkered with using toolbox algorithms to achieve a pseudo-artistic "posterization" effect, like this:

imshow('eddins-horn-1000.png')

I thought the technique was cool and worth showing. I also noticed that the example uses several Image Processing Toolbox functions that haven't been in the product very long, including:

    rgb2lab

    lab2rgb

    imoverlay

    boundarymask

    superpixels

So I realized this would be a good chance to introduce you to some recent toolbox additions that you might not know about.

rgb2lab and lab2rgb

The two functions rgb2lab and lab2rgb were introduced in R2014b. They convert between an RGB space and the CIE L*a*b* space. You could perform this conversion previously using the functions makecform and applycform, but it was awkward. These functions support both sRGB and Adobe RGB (1998).

I have mentioned these functions in a couple of previous blog posts, including "Displaying a color gamut surface" and "Out-of-gamut colors."

boundarymask

The function boundarymask, added in R2016a, produces a binary image whose foreground pixels delineate either the boundaries between adjacent labels, if you give it a label matrix, or the boundaries between the foreground and background, if you give it a binary image. Here's an example.

I = imread('rice.png'); bw = imbinarize(I,'adaptive'); mask = boundarymask(bw); subplot(1,2,1) imshow(I) title('Original image') subplot(1,2,2) imshow(mask) title('Boundary mask') imoverlay

The function imoverlay, added in R2016a, is useful for highlighting a subset of pixel locations in an image. A good example would be to show the boundary mask computed above on top of the original image.

I_overlay = imoverlay(I,mask,'yellow'); imshow(I_overlay)

In my first year of writing this blog (2006), I wrote my own function with this name and submitted it to the File Exchange. Now that the Image Processing Toolbox has its own version, I'll probably remove mine from the File Exchange soon.

superpixels

A "superpixel" is simply a group of connected pixels that have similar colors. Computing superpixels has found a regular place in a variety of image analysis and computer vision tasks. The Image Processing Toolbox function superpixels, introduced in R2016a, computes these groups. Here's an example that computes the superpixels and then uses both boundarymask and imoverlay to visualize them.

A = imread('kobi.png'); L = superpixels(A,1500); mask = boundarymask(L); B = imoverlay(A,mask,'cyan'); clf imshow(B) Superpixel Posterization

The superpixel posterization method, as implemented by Alex, starts by using superpixels to compute clusters of like pixels.

Here's how it works.

A = imread('eddins-horn.png'); imshow(A) title('Original image')

Next, compute and visualize the superpixel clusters.

[L,N] = superpixels(A,1000); BW = boundarymask(L); imshow(imoverlay(A,BW,'cyan'))

In the next step, I want to replace the pixels in each superpixel cluster with the mean of the cluster's colors. But I want to compute the mean in L*a*b* space, so I start by converting from RGB to L*a*b*.

Alab = rgb2lab(A);

I'll use the function label2idx to compute the indices of the pixels in each superpixel cluster. That will let me access the red, green, and blue component values using linear indexing.

pixel_idx = label2idx(L);

For each of the N superpixel clusters, use linear indexing to access the red, green, and blue components, compute the corresponding means, and insert those mean values into the corresponding pixel positions in the L*a*b* output image.

Aplab = Alab; Ln = numel(L); for k = 1:N idx = pixel_idx{k}; Aplab(idx) = mean(Alab(idx)); Aplab(idx+Ln) = mean(Alab(idx+Ln)); Aplab(idx+2*Ln) = mean(Alab(idx+2*Ln)); end

Finally, convert back to RGB space.

Ap = lab2rgb(Aplab); imshow(Ap)

Categories: Blogs

Feret Properties – Wrapping Up

Matlab Image processing blog - 2018, April 17 - 11:11

Today I want to finish up my long-running discussion of Feret diameters. (Previous posts: 29-Sep-2017, 24-Oct-2017, 20-Feb-2018, and 16-Mar-2018.)

Recall that the Feret diameter measures the extent of an object along a particular direction. The diagram below illustrates the concept. 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.

The maximum Feret diameter and minimum Feret diameter measure the maximum and minimum width of an object. In previous posts, I talked about how to identify antipodal vertex pairs from the set the convex hull vertices to speed up the process of finding these maximum and minimum measures. I also considered the various assumptions one can make about the shape of an individual pixel and how those assumptions can affect the results. (In the rest of this post, I'll assume a diamond pixel shape, as suggested by Cris.)

Let's look at these several measurements for a particular object.

bw = imread('shape.png'); imshow(bw) visualizeFeretProperties(bw)

The first diagram above shows the maximum Feret diameter and its orientation. It also shows the Feret diameter at the angle that is orthogonal to the maximum diameter.

The second diagram is similar, except that it shows the minimum Feret diameter instead of the orthogonal diameter. You can see that they are not the same, and, in general, they won't be.

The third diagram shows the minimum-area bounding box, which can be found using a search procedure similar to the minimum Feret diameter. In this diagram, notice that the bounding box is not exactly aligned with the direction of the maximum Feret dimension. In general, they won't necessarily be aligned, and then the length of the minimum-area bounding box will be less than the maximum Feret dimension.

Below is a function I wrote that adds Feret diameter properties to the table returned by regionprops (using the 'table' option). It makes use of the algorithmic functions that you can find in my previous posts.

T = regionprops('table',bw,'PixelList') T=1×1 table PixelList _________________ [163875×2 double] T = feretProperties(T) T=1×12 table PixelList MaxFeretDiameter MaxFeretDiameterEndpoints MaxFeretDiameterOrientation MinFeretDiameter MinFeretDiameterTrianglePoints MinFeretDiameterOrientation OrthogonalDiameter OrthogonalDiameterLowerPoints OrthogonalDiameterUpperPoints MinAreaBoundingBox MinAreaBoundingBoxArea _________________ ________________ _________________________ ___________________________ ________________ ______________________________ ___________________________ __________________ _____________________________ _____________________________ __________________ ______________________ [163875×2 double] 781.13 [2×2 double] -41.835 317.08 [3×2 double] -143.8 331.18 [1×2 double] [1×2 double] [5×2 double] 2.4321e+05

I hope that at least a few of you enjoyed this diversion into algorithms associated with object shape measurement.

Appendix: Functions Usedfunction T = feretProperties(T) % Copyright 2017-2018 The MathWorks, Inc. maxfd = zeros(height(T),1); maxfd_endpoints = cell(height(T),1); maxfd_orientation = zeros(height(T),1); minfd = zeros(height(T),1); minfd_triangle_points = cell(height(T),1); minfd_orientation = zeros(height(T),1); minod = zeros(height(T),1); minod_lower_points = cell(height(T),1); minod_upper_points = cell(height(T),1); minbb = cell(height(T),1); minbb_a = zeros(height(T),1); for k = 1:height(T) pixels = T.PixelList{k}; V = pixelHull(pixels,'diamond'); pairs = antipodalPairs(V); [maxfd(k),maxfd_endpoints{k}] = maxFeretDiameter(V,pairs); points = maxfd_endpoints{k}; e = points(2,:) - points(1,:); maxfd_orientation(k) = atan2d(e(2),e(1)); [minfd(k),minfd_triangle_points{k}] = minFeretDiameter(V,pairs); points = minfd_triangle_points{k}; e = points(2,:) - points(1,:); thetad = atan2d(e(2),e(1)); minfd_orientation(k) = mod(thetad + 180 + 90,360) - 180; [minod(k),minod_lower_points{k},minod_upper_points{k}] = ... feretDiameter(V,maxfd_orientation(k)+90); [minbb{k},minbb_a(k)] = minAreaBoundingBox(V,pairs); end T.MaxFeretDiameter = maxfd; T.MaxFeretDiameterEndpoints = maxfd_endpoints; T.MaxFeretDiameterOrientation = maxfd_orientation; T.MinFeretDiameter = minfd; T.MinFeretDiameterTrianglePoints = minfd_triangle_points; T.MinFeretDiameterOrientation = minfd_orientation; T.OrthogonalDiameter = minod; T.OrthogonalDiameterLowerPoints = minod_lower_points; T.OrthogonalDiameterUpperPoints = minod_upper_points; T.MinAreaBoundingBox = minbb; T.MinAreaBoundingBoxArea = minbb_a; end function [bb,A] = minAreaBoundingBox(V,antipodal_pairs) % 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); A = Inf; thetad = []; 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 are possibly on the minimum-area % bounding box, with points pt1 and pt2 forming an edge coincident with % the bounding box. Call the height of the triangle with base % pt1-pt2 the height of the bounding box, h. h = triangleHeight(pt1,pt2,pt3); pt1pt2_direction = atan2d(pt2(2)-pt1(2),pt2(1)-pt1(1)); w = feretDiameter(V,pt1pt2_direction); A_k = h*w; if (A_k < A) A = A_k; thetad = pt1pt2_direction; end end end % Rotate all the points so that pt1-pt2 for the minimum bounding box points % straight up. r = 90 - thetad; cr = cosd(r); sr = sind(r); R = [cr -sr; sr cr]; Vr = V * R'; xr = Vr(:,1); yr = Vr(:,2); xmin = min(xr); xmax = max(xr); ymin = min(yr); ymax = max(yr); bb = [ ... xmin ymin xmax ymin xmax ymax xmin ymax xmin ymin ]; % Rotate the bounding box points back. bb = bb * R; end function h = triangleHeight(P1,P2,P3) % Copyright 2017-2018 The MathWorks, Inc. h = 2 * abs(signedTriangleArea(P1,P2,P3)) / norm(P1 - P2); end function area = signedTriangleArea(A,B,C) % Copyright 2017-2018 The MathWorks, Inc. area = ( (B(1) - A(1)) * (C(2) - A(2)) - ... (B(2) - A(2)) * (C(1) - A(1)) ) / 2; end function [d,V1,V2] = feretDiameter(V,theta) % Copyright 2017-2018 The MathWorks, Inc. % Rotate points so that the direction of interest is vertical. alpha = 90 - theta; ca = cosd(alpha); sa = sind(alpha); R = [ca -sa; sa ca]; % Vr = (R * V')'; Vr = V * R'; y = Vr(:,2); ymin = min(y,[],1); ymax = max(y,[],1); d = ymax - ymin; if nargout > 1 V1 = V(y == ymin,:); V2 = V(y == ymax,:); end function [d,end_points] = maxFeretDiameter(P,antipodal_pairs) % Copyright 2017-2018 The MathWorks, Inc. if nargin < 2 antipodal_pairs = antipodalPairs(P); end v = P(antipodal_pairs(:,1),:) - P(antipodal_pairs(:,2),:); D = hypot(v(:,1),v(:,2)); [d,idx] = max(D,[],1); P1 = P(antipodal_pairs(idx,1),:); P2 = P(antipodal_pairs(idx,2),:); end_points = [P1 ; P2]; end function [d,triangle_points] = minFeretDiameter(V,antipodal_pairs) % 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

Categories: Blogs

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.

MP3

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.

MP3

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.

MP3

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

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 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

Hey, Wellington! I’m headed your way!

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.

MP3

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.

MP3

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

Adelaide: Mar 4-6: Adelaide Festival


Wellington: Mar 9-11: Writers and Readers Week

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

Categories: Blogs