Circle with Alternating coloured segments

I wanted to reverse engineer this colour wheel code so that I can specify what colour each segment has: segment 1 - green, segment 2-13, red and segment 14-26, black (like a roulette wheel). I'm a little stuck on how to approach this (I've experimented with a simpler code to see if I could use cartesian only but can't see a way to merge them):
% Set parameters (these could be arguments to a function)
rInner = 80; % inner radius of the colour ring
rOuter = 200; % outer radius of the colour ring
ncols = 27; % number of colour segments
% Get polar coordinates of each point in the domain
[x, y] = meshgrid(-rOuter:rOuter);
[theta, rho] = cart2pol(x, y);
% Set up colour wheel in hsv space
hue = (theta + pi) / (2 * pi); % hue into range (0, 1]
hue = ceil(hue * ncols) / ncols; % quantise hue
saturation = ones(size(hue)); % full saturation
brightness = double(rho >= rInner & rho <= rOuter); % black outside ring
% Convert to rgb space for display
rgb = hsv2rgb(cat(3, hue, saturation, brightness));
% Check result
imshow(rgb);
My experiment code:
figure
N = 37;
a = linspace(0, 2*pi, N*10);
r = 1;
x = r*cos(a);
y = r*sin(a);
plot(x, y)
hold on
plot([zeros(1,N); x(1:10:end)], [zeros(1,N); y(1:10:end)])
axis equal
axis off
Additionally I was wondering what tweaks I would need to make so that I can perform a matrix transformation to it (For a rotation animation):
function rShape = rotateShape(shape, a)
%Rotates shape by angle a
%Matrix operation to rotate shape
rShape = [cos(a) -sin(a); sin(a) cos(a)] * shape;
end

 Réponse acceptée

Here's one example. I'm sure this can be simplified. It's flexible enough that you can adapt it for different standard wheel configurations.
% Set parameters (these could be arguments to a function)
rInner = 150; % inner radius of the colour ring
rOuter = 200; % outer radius of the colour ring
% this is the clockwise sequence for a european pattern single-green wheel
slotnums = [0 32 15 19 4 21 2 25 17 34 6 27 13 36 11 30 8 23 10 5 24 16 33 1 20 14 31 9 22 18 29 7 28 12 35 3 26];
slotidx = [1 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2];
CT = [0 0.5 0; 0.1 0.1 0.1; 0.5 0 0]; % make up some colors
nslots = numel(slotnums); % number of colour segments
offset = 45; % specify angle offset (degrees)
% Get polar coordinates of each point in the domain
[x, y] = meshgrid(-rOuter:rOuter);
[theta, rho] = cart2pol(x, y);
% generate segments
outpict = mod(rad2deg(theta)+offset,360)/360; % normalize
outpict = ceil(outpict * nslots); % quantise
outpict = ind2rgb(outpict,CT(slotidx,:));
% mask off annular region
mk = double(rho >= rInner & rho <= rOuter);
outpict(repmat(~mk,[1 1 3])) = 0;
% show result
imshow(outpict); hold on
% add numbers
textrad = rInner + 0.65*(rOuter-rInner); % or pick some radius
th0 = 360/nslots;
th = linspace(0,360-th0,nslots) - offset + th0/2;
cen = size(outpict(:,:,1))/2;
for k = 1:numel(th)
x = textrad*cosd(th(k)) + cen(2);
y = textrad*sind(th(k)) + cen(1);
ht = text(x,y,sprintf('%d',slotnums(k)));
ht.Color = [1 1 1];
ht.FontSize = 10;
ht.FontWeight = 'bold';
ht.HorizontalAlignment = 'center';
ht.Rotation = 270-th(k);
end

8 commentaires

David
David le 6 Déc 2022
Modifié(e) : David le 6 Déc 2022
Thank you so much for this! I was also wondering how I would go about converting the polar coordinates to a cartesian (or plotting it on a cartesian plane)? I'm planning to animate a roller ball for the wheel (trying to adapt the code so it isn't just a carbon copy).
EDIT:
Added some of my own comments to better understand your original code, managed to show the wheel on cartesian. Not sure what to do for the numbers. (Also if you mind explaining what the quatize and repmat are doing (why was [1 1 3] used instead of any other repeat matrix)?
% Set parameters (these could be arguments to a function)
rInner = 150; % inner radius of the colour ring
rOuter = 200; % outer radius of the colour ring
% this is the clockwise sequence for a european pattern single-green wheel
slotnums = [0 32 15 19 4 21 2 25 17 34 6 27 13 36 11 30 8 23 10 5 24 16 33 1 20 14 31 9 22 18 29 7 28 12 35 3 26];
slotidx = [1 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2];
%slotidx used to index colours, 1 - green for 0, 2 - blacks, 3 - reds. Each
%number from slotnums is assigned a corresponding slotidx
CT = [0 0.5 0; 0.1 0.1 0.1; 0.5 0 0]; % Colours (percentage RGB scale, contains luminosity of colour)
nslots = numel(slotnums); % number of colour segments
for i = 0:5:25
offset = i; % specify angle offset (degrees)
% Use meshgrid to define a coordinate plane
[x, y] = meshgrid(-rOuter:rOuter);
% Get polar coordinates of each point in the domain
[theta, rho] = cart2pol(x, y);
% generate segments
segm = mod(rad2deg(theta)+offset,360)/360; % normalize
segm = ceil(segm * nslots); % quantise
segm = ind2rgb(segm,CT(slotidx,:)); %assigns rgb colour to each segment
% Define boundaries of the annular region (masking off)
mk = double(rho >= rInner & rho <= rOuter);
%
segm(repmat(~mk,[1 1 3])) = 0;
% show result
%imshow(segm);
imagesc(-rOuter: rOuter, -rOuter: rOuter, segm);
axis on
axis square
hold on
% add numbers
textrad = rInner + 0.65*(rOuter-rInner); % or pick some radius
th0 = 360/nslots;
th = linspace(0,360-th0,nslots) - offset + th0/2;
cen = size(segm(:,:,1))/2;
for k = 1:numel(th)
x = textrad*cosd(th(k)) + cen(2);
y = textrad*sind(th(k)) + cen(1);
ht = text(x,y,sprintf('%d',slotnums(k)));
ht.Color = [1 1 1];
ht.FontSize = 10;
ht.FontWeight = 'bold';
ht.HorizontalAlignment = 'center';
ht.Rotation = 270-th(k);
end
pause(0.2)
end
DGM
DGM le 6 Déc 2022
Modifié(e) : DGM le 6 Déc 2022
Everything already is being plotted in cartesian coordinates. It's just constructed using polar coordinates.
I noticed that I didn't really pay attention to the need to rotate things. It might have been better to do this with patch objects now that I think about that. I suppose it can still be done with an image. Here's a rough example of how you could set it up so that you can rotate everything without needing to reconstruct it every time.
% Set parameters (these could be arguments to a function)
rInner = 150; % inner radius of the colour ring
rOuter = 200; % outer radius of the colour ring
% this is the clockwise sequence for a european pattern single-green wheel
slotnums = [0 32 15 19 4 21 2 25 17 34 6 27 13 36 11 30 8 23 10 5 24 16 33 1 20 14 31 9 22 18 29 7 28 12 35 3 26];
slotidx = [1 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2];
CT = [0 0.5 0; 0.1 0.1 0.1; 0.5 0 0]; % make up some colors
nslots = numel(slotnums); % number of colour segments
startangle = 45; % specify angle offset (degrees)
% Get polar coordinates of each point in the domain
[x, y] = meshgrid(-rOuter:rOuter);
[theta, rho] = cart2pol(x, y);
% generate segments
outpict = mod(rad2deg(theta)+startangle,360)/360; % normalize
outpict = ceil(outpict * nslots); % quantise
outpict = ind2rgb(outpict,CT(slotidx,:));
% mask off annular region
mk = double(rho >= rInner & rho <= rOuter);
outpict(repmat(~mk,[1 1 3])) = 0;
% add black matting to axes
imshow(zeros(size(theta))); hold on
% show result
hi = imshow(outpict);
hg = hgtransform; % the parent for all the objects to be rotated
hi.Parent = hg;
% add numbers
textrad = rInner + 0.65*(rOuter-rInner); % or pick some radius
th0 = 360/nslots;
th = linspace(0,360-th0,nslots) - startangle + th0/2;
cen = size(outpict(:,:,1))/2;
allht = gobjects(numel(th),1); % a handles array
for k = 1:numel(th)
x = textrad*cosd(th(k)) + cen(2);
y = textrad*sind(th(k)) + cen(1);
allht(k) = text(x,y,sprintf('%d',slotnums(k)));
allht(k).Parent = hg;
allht(k).Color = [1 1 1];
allht(k).FontSize = 10;
allht(k).FontWeight = 'bold';
allht(k).HorizontalAlignment = 'center';
allht(k).Rotation = 270-th(k);
end
% let's say you wanted to rotate the graphics object in a loop
% without having to regenerate it all from scratch
% pretend this is inside a loop or something
% ...
groupangle = 5; % amount to rotate (degrees)
mr = makehgtform('zrotate',deg2rad(-groupangle));
mt = makehgtform('translate',[-cen 0]);
mt2 = makehgtform('translate',[cen 0]);
hg.Matrix = mt2*mr*mt;
for k = 1:numel(allht)
allht(k).Rotation = allht(k).Rotation+groupangle;
end
drawnow
% ...
... I'm sure that can be simplified, but it's a start.
David
David le 6 Déc 2022
For simplicity I don't mind regenerating the image by by setting the angle offset to a variable (i) then placing it in an interative for loop to get it rotate. Though I was wondering how you could recenter the circle so that the wheel is in the position in the image from my code above (I noticed that my y axis is inverted)
DGM
DGM le 6 Déc 2022
Oh I didn't notice you'd updated. Your idea to set the position of the image is good. If the image is centered on [0 0], then the text objects won't need to be offset by cen (which would otherwise be the center of the image). If you were using hgtransform() to do the rotations, you could then also avoid the two translations.
Yeah, the y-axis will be flipped. Dealing with that depends on what you want. Normally, the origin of images is the NW corner, so when you call imshow() or image(), the 'ydir' property of the axis is set to 'reverse'. If you want your axes to not be flipped, you can set the ydir property to 'normal', and do flipud() on the image before displaying it.
Final question, would you mind explaining what the following lines do:
outpict = ceil(outpict * nslots); % quantise <--- not familiar with this concept
outpict = ind2rgb(outpict,CT(slotidx,:));
% mask off annular region
mk = double(rho >= rInner & rho <= rOuter);
outpict(repmat(~mk,[1 1 3])) = 0;
Also not sure how you've chosen the matrix [1 1 3] for repmat
I suppose I should've redid that comment.
Consider the example which is more similar to the original code you posted:
% say we have a variable which is continuous
indata = linspace(0,1,1000);
nslots = 10;
outdata = ceil(indata * nslots)/nslots; % quantise
plot(indata); hold on
plot(outdata)
In this case, we're using scaling, rounding, and rescaling of a normalized set of data to quantize it. When I adapted the original code, my intent changed. Instead of merely trying to quantize hue, I'm trying to generate an indexed image from scratch. The output image should be a 37 step ramp from 1 to 37 instead of a 37 step ramp from 0 to 1 as above. So the line that says "quantize" is really "quantize and denormalize" in a way, since it's not renormalized after rounding.
outpict = ceil(outpict * nslots); % quantize (denormalized [1 37])
As I mentioned, I'm constructing an indexed image, so I've defined a couple parameters, slotidx and CT. The color palette is defined by CT, but which color is assigned to each slot is defined by slotidx. This just makes it easier to adapt to different wheel configurations without having to write out a repetitive long color table. Instead, the 37x3 color table is generated inline in the call to ind2rgb().
outpict = ind2rgb(outpict,CT(slotidx,:));
At this point, the image is an RGB (MxNx3) image that just looks like a big pinwheel pattern. In order to black out the inner and outer regions, I fill them with zero.
% mask off annular region
mk = double(rho >= rInner & rho <= rOuter);
outpict(repmat(~mk,[1 1 3])) = 0;
The reason for using repmat here is simple, but easily missed. If I were to do this addressing on the LHS of the assignment:
outpict(~mk) = 0;
... then that would only address the first channel (the red channel) of the RGB image. One way to fix that is to expand the mask so that it's the same size as the image in all dimensions. The second argument to repmat() is the number of times to replicate the input along each dimension. So [1 1 3] specifies that the output contains the MxNx1 mask tiled once along dims 1 and 2 and three times on dim 3 . The result is a MxNx3 mask.
David
David le 6 Déc 2022
Modifié(e) : David le 6 Déc 2022
Thank you so much for the explanation!
Found a weird behaviour. When using the imagesc line:
imagesc(-rOuter: rOuter, -rOuter: rOuter, segm);
I can scale the axis but not the image (I can't get it to create a zoom out effect), is that because the wheel has its own indepedent coordinate plane when I defined it as
[x, y] = meshgrid(-rOuter:rOuter);
The numbers seem to be affected by the imagesc line (they appear to be smaller as the axis covers a greater range, but the wheel remains the same in scale)
DGM
DGM le 6 Déc 2022
Modifié(e) : DGM le 6 Déc 2022
I'm not sure exactly how the text 'fontsize' relates to axes units, but otherwise if you want to be able to adjust the zoom level, you might do that by manipulating xlim and ylim. If xlim and ylim are equal to the limits given in the call to imagesc(), then the image will always fill the axes regardless of its scale.
If you don't necessarily want to adjust the zoom, but just want more padding around the wheel, then you can just make the image bigger to start with.
% ...
maxr = 300; % the maximum image radius (independent of the wheel)
% ...
[x, y] = meshgrid(-maxr:maxr);
% ...
imagesc(-maxr:maxr, -maxr:maxr, segm);
% ...

Connectez-vous pour commenter.

Plus de réponses (0)

Catégories

En savoir plus sur Graphics Object Properties dans Centre d'aide et File Exchange

Question posée :

le 5 Déc 2022

Modifié(e) :

DGM
le 6 Déc 2022

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by