Array Comprehension Problem

So completely new to coding and I’m currently going through the MIT courseware intro to computational thinking in Julia and I’m pretty confused how this piece of code works:

Write the third method noisify(image::AbstractMatrix, s) to noisify each pixel of an image. This function should be a single line!

function noisify(image::AbstractMatrix, s)
return [noisify(x, s) for x in image]
end

in which a slider is used to adjust noise and the final function, where philip_head is the image being used.

@bind philip_noise Slider(0:0.01:1, show_value=true)
noisify(philip_head, philip_noise)

So to be honest I used chatGPT to write the code for me because I was at a loss for what to write. I’m having trouble understanding how [noisify(x ,s) for x in image] works. I added noise to the other function, which called for a random integer between -s and s to be added:

function noisify(x::Number, s)
s = rand()*rand((1,-1))
noise = x+s
return noise
end

which makes sense to me, kinda. But [noisify(x,s) for x in image] is making absolutely no sense to me right now lmao. Is it adding s (Adding 0-1 per the scale to the RGB values respectively) to every value of the matrix?

I doubt using ChatGPT will ever help you learn. In this instance it has written a nonsense function, and the fact that you don’t know how to identify that it’s nonsense is exactly why you shouldn’t use it.

About array comprehension, the natural language version of [f(x) for x in y] is “for every element x in y, put f(x) in an array”. Hope that helps.

1 Like

The array comprehension is just a for loop. For each pixel x in the image, we will run some function.

julia> const corgi_url = "https://user-images.githubusercontent.com/6933510/107239146-dcc3fd00-6a28-11eb-8c7b-41aaf6618935.png"
"https://user-images.githubusercontent.com/6933510/107239146-dcc3fd00-6a28-11eb-8c7b-41aaf6618935.png"

julia> image = load(corgi_url);

julia> [println(x) for x in corgi];
RGB{N0f8}(0.157,0.141,0.106)
RGB{N0f8}(0.157,0.141,0.106)
RGB{N0f8}(0.157,0.141,0.106)
RGB{N0f8}(0.157,0.141,0.106)
RGB{N0f8}(0.145,0.129,0.094)
RGB{N0f8}(0.145,0.129,0.094)
RGB{N0f8}(0.145,0.129,0.094)
...

The above array comprehension is similar to the following loop.

values = []
for x in corgi
    val = println(x)
     push!(values, val)
end
return values

The function you (or ChatGPT) proposed has some issues.

function noisify(x::Number, s)
    s = rand()*rand((1,-1))
    noise = x+s
    return noise
end

The above function ignores the argument s. It also does not generate a random value between -s and s.

In Julia, you can generate a random integer between s and -s as follows:

julia> let s = 5
           rand(-s:s)
       end
-3

julia> let s = 5
           rand(-s:s)
       end
-4

julia> let s = 5
           rand(-s:s)
       end
-1

julia> let s = 5
           rand(-s:s)
       end
-4

julia> let s = 5
           rand(-s:s)
       end
5

I’m not sure what image is your in space, but it appears to be a simple matrix of numbers rather than a matrix of RGB values. A matrix of numbers can be interpreted as a gray scale image going from 0 (black) to 1 (white).

The slider above suggests that s is a a floating point value (Float64) between 0 and 1. Thus it seems we do not want an integer between -s and s but a floating point value in that range.

rand() by itself will return a pseudo-random floating point value between 0 and 1. Let’s manipulate this to be a value between -1 and 1. First, let’s multiply this rand() by 2. Now we have a psuedo-random number between 0 and 2.

julia> [rand()*2 for x in 1:5]
5-element Vector{Float64}:
 0.29105644396254204
 0.4805402794429039
 0.7185490585199521
 1.0051175366207994
 1.8548283774237921

Second, let’s subtract one so that the range of the psuedo-random number is between -1 and 1.

julia> [rand()*2-1 for x in 1:5]
5-element Vector{Float64}:
 -0.7154356748800863
  0.5931495061220962
 -0.5112898446445013
  0.5877524288483504
 -0.39967985881552304

Third, let’s scale this so that the random values are between -s and s. To do so, we just multiply the above result by s:

julia> let s = 0.5
           [s*(rand()*2-1) for x in 1:5]
       end
5-element Vector{Float64}:
  0.010240801151369938
  0.3113220164047772
  0.42303223201915907
 -0.22238035973003045
 -0.322278836649768

The homework assignment suggests clamp.

function noisify(x::Number, s)
    noise = s*(rand()*2-1)
    return clamp(x + noise, 0, 1)
end

We can apply that function direct to values to generate “nosified” values.

julia> [noisify(x,0.1) for x in 0:0.1:1]
11-element Vector{Float64}:
 0.04185649966271443
 0.059601413880822074
 0.17569873187297833
 0.23385349040162423
 0.37913057544096834
 0.5253420496055848
 0.5290349279141047
 0.6933767312224085
 0.8389770893749297
 0.9442019869910827
 0.9293120465194884

To noisify the image, however, you need to write a version of noisify for RGB values. I’m not going to describe exactly how to do this part since it’s the homework assignment, but I’m to describe how to work with RGB values.

Below I extract the first pixel of the image and find it’s red, green, and blue values.

julia> px = image[1] # get the first pixel
RGB{N0f8}(0.157,0.141,0.106)

julia> px.r
0.157N0f8

julia> px.g
0.141N0f8

julia> px.b
0.106N0f8

Notice that I can apply the noisify function above.

julia> noisify(px.r, 0.1)
0.10024330653215385

julia> noisify(px.g, 0.1)
0.2120751894938211

julia> noisify(px.b, 0.1)
0.0671814168720399

I can then create a new RGB value in the following way.

julia> noisified_px = RGB{N0f8}(
           noisify(px.r, 0.1),
           noisify(px.g, 0.1),
           noisify(px.b, 0.1)
       )
RGB{N0f8}(0.188,0.149,0.122)

You’ll notice that you cannot apply noisify direct to the pixel:

julia> noisify(px, 0.1)
ERROR: MethodError: no method matching noisify(::RGB{N0f8}, ::Float64)

Closest candidates are:
  noisify(::Number, ::Any)
   @ Main REPL[139]:1

To do so, you need to define noisify as follows:

function noisify(px::RGB, s)
   # create a noisified RGB value
   # return the value
end

When complete, then you can do the following:

julia> noisify(px, 0.1)
RGB{N0f8}(0.255,0.09,0.078)

julia> noisify(px, 0.1)
RGB{N0f8}(0.063,0.11,0.161)

Finally, then you can use the array comprehension.

julia> [noisify(x,0.1) for x in image]
864×700 Array{RGB{N0f8},2} with eltype RGB{N0f8}:
 RGB{N0f8}(0.239,0.055,0.137)  …  RGB{N0f8}(0.78,0.655,0.706)
 RGB{N0f8}(0.078,0.075,0.188)     RGB{N0f8}(0.604,0.729,0.765)
...
2 Likes

Much love !!!
Thanks for not solving the homework but giving me the knowledge instead

1 Like