4

I have a image pixel prediction array that is of size 9085x10852. I want to get a 10x10 block around each pixel. if the center pixel value is different from the majority pixel values in the block, then replace the center pixel value with the majority value. Can anyone help me please

Lisa Mathew
  • 305
  • 4
  • 18
  • SO is not a free coding site – Mad Physicist Aug 28 '18 at 04:03
  • 2
    A clarification... a 10x10 block does not have a core cell. only odd numbered rows/columns will. if `a` is an array, what is your focal cell...a[4,4]? a[5, 5]? a[4,5] etc – NaN Aug 28 '18 at 06:25
  • Another clarification: how do you define the majority pixel value in the block.. mean ? relative frequency? – kevinkayaks Aug 28 '18 at 07:00
  • It's look like the [median_filter](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.median_filter.html#scipy.ndimage.median_filter)? – xdze2 Aug 28 '18 at 10:19
  • @NaN yes it has to be a odd block. – Lisa Mathew Aug 28 '18 at 17:27
  • @kevinkayaks if say the center pixel value is 3 and pixels around it is all 4 then I want to change the value of the center pixel to 4. If there are more than one value around it, then take the one that occurs the most. – Lisa Mathew Aug 28 '18 at 17:29
  • That's like coding `if a != b then a=b`. Whatever happens `a` ends up equal to `b` so there is no need for an `if`. – Mark Setchell Aug 28 '18 at 22:17

3 Answers3

6

I was digging around in scikit-image looking for something else today and if you dig down into scikit-image.filters and then even further into rank, you come across modal()! See documentation here.

I used the same random seed as @Tonechas to generate comparable results:

import numpy as np
from skimage.morphology import rectangle   # for Structuring Elements (e.g. disk, rectangle)
from skimage.filters.rank import modal     # the puppy we want

# Same seed for directly comparable results
np.random.seed(329)

# Sample array/image
arr = np.random.randint(low=0, high=10, size=(6, 8), dtype=np.uint8)

# Run the filter with a 5x5 rectangular Structuring Element
result = modal(arr,rectangle(5,5))

print(result)

array([[9, 2, 0, 0, 0, 2, 4, 5],
       [1, 1, 0, 0, 0, 2, 5, 2],
       [1, 1, 0, 5, 5, 5, 5, 5],
       [1, 1, 1, 5, 5, 5, 4, 5],
       [1, 1, 1, 1, 5, 5, 4, 5],
       [1, 1, 5, 5, 5, 5, 4, 4]], dtype=uint8)

Keywords: Python, numpy, scikit, skimage, image processing, process images, median, mode, modal, structuring element, morphology, filter, filters, rank.

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • Your solution is neater (and probably faster) than mine. I think this should be the accepted answer. You got my vote. – Tonechas Sep 13 '18 at 22:58
5

As you seem a little unsure/inconsistent about the details of your question, I would like to suggest a very simple, alternative solution, not in Python but too big for a comment, that helps you and others explore whether you really want this and what you actually want - before anyone spends hours coding Python.

I suggest you use ImageMagick which is installed on most Linux distros and is available for macOS and Windows. So, just in Terminal, you can make a sample image which is black with a white square in the middle, like this:

convert -size 100x100 xc:black -fill white -draw "rectangle 10,10 90,90" test.png

enter image description here

Now try your filter and you can see the corners are rounded:

convert test.png -statistic mode 10x10 result.png

enter image description here

Now try again with a bigger "radius":

convert test.png -statistic mode 20x20 result.png

enter image description here

Maybe you can experiment with that and see if it does what you want before anyone wastes too much time coding anything.

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • 1
    For completeness, here is the doc for [`-statistic mode`](https://www.imagemagick.org/script/command-line-options.php#statistic) : "replace each pixel with the mode (most frequent) value per channel in neighborhood" – xdze2 Aug 29 '18 at 13:06
  • @xdze2 Thank you - sometimes I forget to add references, and they do make peoples' lives easier. – Mark Setchell Aug 29 '18 at 13:17
4

One possible approach could be that of defining a function that replaces the central value by the mode...

import numpy as np
from scipy.ndimage import generic_filter

def most_frequent(x):
    central = x[x.size//2] 
    values, counts = np.unique(x, return_counts=True)
    max_freq = counts.max()
    modes = values[counts == max_freq]
    if central in modes:
        return central
    else:
        return modes[0]

... and passing such a function to scipy.ndimage.generic_filter.

Demo

In [143]: r = 2

In [144]: block_size = (2*r + 1, 2*r + 1)

In [145]: block_size
Out[145]: (5, 5)

In [146]: np.random.seed(329)

In [147]: arr = np.random.randint(low=0, high=10, size=(6, 8), dtype=np.uint8)

In [148]: arr
Out[148]: 
array([[9, 6, 2, 2, 0, 5, 6, 4],
       [9, 7, 0, 2, 0, 5, 4, 2],
       [1, 3, 8, 1, 4, 6, 5, 2],
       [5, 1, 7, 8, 5, 7, 0, 2],
       [8, 1, 0, 5, 4, 5, 4, 5],
       [4, 1, 5, 3, 6, 9, 4, 3]], dtype=uint8)

In [149]: generic_filter(arr, function=most_frequent, 
     ...:                size=block_size, mode='constant', cval=np.nan)
     ...: 
Out[149]: 
array([[9, 2, 2, 2, 0, 2, 4, 5],
       [9, 1, 0, 2, 0, 2, 5, 2],
       [1, 1, 0, 5, 5, 5, 5, 5],
       [1, 1, 1, 5, 5, 5, 4, 5],
       [1, 1, 1, 5, 5, 5, 4, 5],
       [1, 1, 5, 5, 5, 5, 4, 4]], dtype=uint8)

Notice that this code might take a while to run on a 9085×10852 array.

Tonechas
  • 13,398
  • 16
  • 46
  • 80
  • strangely the Scipy [mode function](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mode.html) is much slower (and do not deals with the central point) – xdze2 Aug 29 '18 at 19:18