Grouping surfaces based on color

Hello Everyone,
I am trying to create a Pixel art from an image by taking pixel values . Now i would like to seperate the surfaces based on colors Blue, White and Yellow.
Could some one explain me a way to do that


3_image sampling.dyn (29.5 KB)

You’ll probably want GroupByKey.

1 Like

Hello Nick,
Thankyou for your reply
The List.group by key should be able to group them by colors
But the problem is , here list.unique items node show that there are 45 variation of colors then i will get 45 groups which is not what i am looking for.
I am looking for three groups of surfaces, which are seperated by three colors (Blue, yellow and White) so that i can send them to Autodesk alias separately

@siddhardha.mjm try to unify your colors using an if statement to get only 3 colors, something like this:

2 Likes

You are receiving 45 values because there are multiple colors as the image blends where the two colors are adjacent. White gets darker around the blue, blue gets whiter around the white, blue gets darker around the yellow, yellow gets darker around the blue. This isn’t consistent but will vary oh so slightly along the edges. This is just how pixel art works - otherwise things look pixelated. The resolution of your sampling in Dynamo will also impact this. To resolve the issue your best bet will be to utilize a pixel manipulation program - my personal favorite is GIMP, as it is powerful, has a significant amount of features, fairly stable, and open source (albeit slower than other offerings). There is a filter called posterize in GIMP which will reduce the color count to the N most common colors. Effectively you go from 256 bit color depth to at most N per color channel. Once that is done you can export the GIMP file to a jpg or similar without dithering to get the reduced color set and utilize that file for the sampling in Dynamo. The key will be to ensure no dithering or other smoothing or compression technique is used in the file type you export to, and to sample in Dynamo so you are pulling the same number of pixels as you have in your image (the aspect ratio of each surface would be square).

But since you asked about doing this in Dynamo… You would likely want to utilize a pixel manipulation package in a Python node. There are just too many complexities at play for what you want to do. OpenCV might be a good choice to start looking. Not an easy task though.

If you are feeling REALLY adventurous, you could attempt to do this with native Dynamo. But be warned this likely won’t be perfect for all situations, and may even fail entirely based on an unseen flaw in my logic. So if you REALLY want to do this in Dynamo… my initial thoughts are below.

Take all the colors and group them by key as you had before. Count how many are in each color set, and take the 3 most common and set them aside. We could then take each of the remaining color sets will need to be convert it to the closest of these colors. But how can we compare colors in Dynamo?

Out of the box the color toolsets are quite limited - Dynamo isn’t really built for pixel and color problems but problems in the built environment after all. Because of the spatial aspects of the built environment it does allow geometry manipulation in 3 dimensional space, and if we ignore transparency, colors have 3 values. So we can convert the RGB values of each color set to a point.

x = pixelColor.R;
y = pixelColor.Y;
z = pixelColor.Z;
pnt = Point.ByCoordinates(x,y,z);

You can now get the distance from any point (pnt[i]) to any other point set (pnts[[n,m,o]]).

We can take the three most common points by count, and use those as our basis - called set - and remove them from the points. Now for each point in the points list you can get the distance to the points in the set. Find the value in set which has the least distance, and assign the point to that set.

Once you iterate over all the points but three you should only have 3 points to work with. Converting these points to a color wouldn’t be that hard.

r = pnt.X;
g = pnt.Y;
b = pnt.Z;
col = Color.ByARGB(255,r,g,b);

Hopefully all of this is making sense, as we are about halfway there… the above steps certainly ruined the list structure - there is no direct 1:1 relationship between the sequence of colors we originally worked with, and the sequence of points we built - as soon as we grouped the values we lost the order!

So instead of just converting to geometry, we need to find the most common colors and build our set, then for each pixel (in the unsorted list) we need to find the closest color using our geometric conversion… quite a bit more to do still.

Building a few custom definitions to find the N most common RGB values in a color set is likely in order…

  1. Convert image data to a surface:color pairing using rectangles; or map image data over an existing surface and call it a pixel set.
  2. Find the N most common colors in a pixel set as a color set.
  3. Assign colors from color set to pixel set by nearest color.

A zero touch node set would likely be easiest here as you can define the custom data, but a dictionary returned from a custom node would likely work as well. I may play around this this later, if I have the time. No promises though as it is a big lift.

Hello Tradelie,
Thankyou for the reply
This is what i am looking for . Could you please explain more about the code block you have used in the above image . Some more info will be really helpful

Hello Jacobsmall ,
Thankyou for your explanation .
It looks like using GIMP to reduce the color count to N colors is a good solution . I will try this definitely .

@siddhardha.mjm just analyze the major 3 colors that you are looking for in the image;
Since you are looking for 3 specific colors, Blue, “Light Grey” and Yellow, their RGB goes by (0,0,255),(anything above 128,128,128), and (255,255,0), following these values the constructed If statement in the Code Block checks the “image pixels” and returns only Blue_when a pixel’s color is Blueish(r<128,g<128,blue>128)_, Light Grey or Yellow colors, the Black color is for checking in case the If statement values need more tweaking/ input…hope I explained myself enough

1 Like

That is a great solution if you know the three colors in advance. If you aren’t sure which colors to use or if your colors are less readily distributed (ie: three shades of green, or 12 colors instead of 3) then things get more difficult.

1 Like

Since your colors are still pretty isolated you could try “rounding” to narrow the results. You could also try looking at the most common colors. Both of these options still require some finessing and aren’t perfect, but they’re relatively easy to attempt.

EDIT: You can see my attempt here. I wrote some python to try and match up the original colors with the most common three. It’s not perfect in my case as some gray areas were closer in shade to the blue than the black, but it might work for you.

Python Code
import math
colorMatrix = IN[0]
targetColors = IN[1]
out = []

def compareColors(x,y):
	v = abs(x.Red-y.Red)+abs(x.Green-y.Green)+abs(x.Blue-y.Blue)
	return v

for colorRow in colorMatrix:
	output = []
	for color in colorRow:
		diff = 1000
		new = None
		for target in targetColors:
			value = compareColors(color,target)
			if value < diff:
				diff = value
				new = target
		output.append(new)
	out.append(output)

OUT = out

Hello Tradelie,
Could you tell me what is wrong with my code block . It gives an error and does not show inputs for white and yellow
image

You need an AND function (&&) between each argument.

Hello Nick,

Thankyou for pointing it out

Hello Nick,
Thankyou for this Python solution . As i have no knowledge of programming in Python, i can not change the code as per fututre requiremnts.

There really aren’t any changes you’d be required to make. Though it might be a good place to start learning.

See if the workflow suggested at the post below many years ago works for you

2 Likes

This python code will get the N most frequent colors in a pixel set, and map all colors to the nearest (geometrically speaking) color in the common set.

It seems to work well enough speed wise, at least as long as the pixel sets can be imported, so I figured I would share this as is. You should be able to map these pixels to your current surface set without issue, and use them as a key to group panels. The non-frozen python should have some fairly robust annotations as well for anyone who wants to try and learn by reverse engineering the code.

Hope it helps.

Reduce Image Colors.dyn (37.4 KB)

2 Likes

Hello JacobSmall,
I tried to create a simple image with solid colors to check the grouping . As you mentioned earlier the colors are seperated the right way .
But list.Groupbykey node does not seem to do what i am looking for . I am trying to group the surfaces based on colors what it is assigned and send it to Alias
Could you please take a look at it.


6_image sampling.dyn (35.1 KB)

Remove the List.UniqueItems node which happens right before the Key input in the List.GroupByKey node.

Hello JacobSmall,
Even after removing the list.uniqueitems node, i end with color information in List.groupbykey node groups .
I guess Geometrycolor.ByGeometrycolor node displays only colors visually and does not allow the geometry to pass through.
That is the reason send to alias node gives me an error Non geometry type