Binning a 2D array

Hi,

I am trying to apply my understanding of image binning in Julia (which at its heart is an operation that needs to be performed on a 2D array) . To that end I created a toy array

julia> a = rand(range(0,1,step=1),10,10)
10×10 Array{Int64,2}:
 0  1  0  1  1  0  0  0  1  0
 1  0  1  0  1  0  1  1  0  0
 0  1  0  0  1  1  0  1  0  0
 0  0  0  0  0  1  0  1  1  0
 1  1  1  0  0  0  1  0  1  0
 0  0  1  0  0  1  0  0  0  1
 0  0  1  1  0  1  0  0  1  1
 0  1  0  0  1  0  0  0  1  1
 0  1  1  0  0  0  0  1  0  1
 1  0  1  0  1  1  0  1  1  0

Now lets say, that I want to bin the data by 2 which in this case, would be a 2x2 kernel to that end I could grasp the logic as follows:

b=[]
julia> append!(b,a[1]+a[1,1]+a[2]+a[1,2])/2
1-element Array{Float64,1}:
 1.0
julia> b
1-element Array{Any,1}:
 2

I guess, where I am stuck is coding a loop that will raster the kernel along a height and width of an input array and return a new array that is a fraction of the input size which in this case would be a 5x5 array.

is there a specific question you wanna ask?

Yes, i think the specific question is, how do you raster along the dimensions of an array such that the resultatnt array is a fraction of its size. eg. a 10x10 array is now a 5x5 array because each element in the smaller array is an average of a 2x2 kernel.

ImageTransformations.jl · JuliaImages there’s existing library that can do this.

To continue where you were originally doing, you want to make a b that is 5x5 and then, for each b[i,j] you want to look at the corresponding 2x2 in a, so, if i=1, j=1, you want to look at a[1:2, 1:2]; i=2, j=3 <= a[3:4, 5:6] etc…

3 Likes

This should work for arbitrary dimensions, and also if the axis length is not divisible by the bin size:

import Statistics: mean
map( (axes(a) .|> x->Iterators.partition(x,2)) |> x->Iterators.product(x...) ) do idx
       mean(a[idx...])
end

Thanks! I wanted to try this without libs because I wanted to gain a fundamental understanding of the operation.

1 Like

Thanks for introducing a new Lib (for me) AndiMD!

1 Like

My suggestion is to use CartesianIndices to iterate over a. Note that you can create ranges of CartesianIndexes, e.g.

julia> CartesianIndex(1, 1):CartesianIndex(2, 2)
2×2 CartesianIndices{2,Tuple{UnitRange{Int64},UnitRange{Int64}}}:
 CartesianIndex(1, 1)  CartesianIndex(1, 2)
 CartesianIndex(2, 1)  CartesianIndex(2, 2)

which will probably get you pretty far. As an added tip, arithmetic woks on CartesianIndex as though it were a scalar, as do functions like one, zero, etc, which you may find yourself needing.

2 Likes

If you always want a power-of-2 bin, repeated application of this works:

bin2(a) = a[1:2:end-1, 1:2:end-1] .+ a[2:2:end, 1:2:end-1] .+ a[1:2:end-1, 2:2:end] .+ a[2:2:end, 2:2:end]