# Counting distinct values over sliding windows in images & lists

I am trying to count the number of distinct colours in a $$5\times5$$ box, (a radius 2 filter) at all points over a quantized image. I cannot seem to get anything out of the following code except for a black square:

img = ColorQuantize[ExampleData[{"TestImage", "Peppers"}], 8, Dithering -> False];
dis = ImageFilter[CountDistinct[Flatten[#, 1]] &, img, 2];


I expect each pixel in the image to be replaced with a single non-negative integer telling me how many unique colours are in the radius 2 vicinity of that pixel. It's plain to see you can choose $$5\times5$$ boxes in the peppers image which have more than one colour so the output should look more interesting.

I'd also like to know why this related code for 1D produces all 1's instead of the number of unique elements in a centered window as it slews across the list, and how to correct it:

MovingMap[CountDistinct, {1, 2, 3, 3, 3, 4, 5, 6}, {1, Center}, "Reflected"]


For 1D, I want to achieve with MovingMap the same behaviour you can get with Partition like this:

CountDistinct /@ Partition[{1, 2, 3, 3, 3, 4, 5, 6}, 3, 1, 2, {}]

• In your 1D MovingMap you are asking for a window of size 1, so the inputs to CountDistinct are lists with only one element, so the output is always 1 (you can see what happens by replacing CountDistinct with an undefined f). You would get closer with something like MovingMap[CountDistinct, yourList, Quantity[3, "Events"]], but I am not convinced that you can reproduce the partitioning you get with Partition using MovingMap. Jun 18, 2020 at 15:17
• @MarcoB thanks this adequately fixes the 1D MovingMap case for me. Any idea how to get the distinct colour ImageFilter to work? Jun 18, 2020 at 15:29

In your 1D MovingMap you are asking for a window of size 1, so the inputs to CountDistinct are lists with only one element, so the output is always 1 (you can see what happens by replacing CountDistinct with an undefined f). You would get closer with something like MovingMap[CountDistinct, yourList, Quantity[3, "Events"]], but I am not convinced that you can reproduce the partitioning you get with Partition using MovingMap

In your image processing, the problem stems from the fact that, as the documentation states, "The function f [in ImageFilter] is assumed to return channel values that are normally in the range 0 to 1." CountDistinct does not return a number from 0 to 1. You want to rescale its results so they lie within (0,1). A brute force approach could be the assumption that you could have no more than (2n+1)(2n+1) different colors in an n-neighborhood of your pixel, which in your case is 25, so divide the output of CountDistinct by 25, and then apply ImageAdjust:

dis = With[{n = 2},
ImageAdjust@ImageFilter[CountDistinct[#]/(2 n + 1)^2 &, img, n]
]


• It's worth to note that ImageFilter, with the default value of Interleaving, applies the function separately to each channel, so your image, which looks binary, is actually an RGB image with tuples {1, 1, 1} or {0.3, 0.3, 0.3}. It works in this case because the colors in the image do not e.g. share the R values but differ in the G and B vaues. If that were the case, this would not be the same as comparing colors. Setting Interleaving -> True fixes this, and returns a binary image, as one might expect. Jun 18, 2020 at 15:48
• @C.E. thanks I wasn't aware of Interleaving. Jun 18, 2020 at 15:51
• The result also fairly closely matches EntropyFilter[img, 2] which is nice to see. Jun 18, 2020 at 15:59

I came up with another method. I can replace the colours in the original quantized image with single numbers to form a palletized grayscale image first. Then ImageFilter works and I get the same image as in @MarcoB's answer - seeing it with two different methods is enough confirmation for me that the image is good.

uniquecolours = Flatten[ImageData[img], 1] // DeleteDuplicates;
pltimg = Image@Map[FirstPosition[uniquecolours, #] &, ImageData[img], {2}];
dis = ImageFilter[CountDistinct, pltimg, 2] // ImageAdjust;