Hello, I spent the last three days learning Nim, and just today coded the application I wanted to do (an image processing filter).
As I wrote what I wanted as math, I wanted to code it like math (functionally and using for loops), but Python was too slow, thus I looked for an alternative language and found Nim. I have read some critics saying that Nim was difficult to debug and not that well documented, which is normal given the amount of people involved.
Sadly, these critics were right: I expected something like TypeScript, where the VSCode extension works indistinguishable from the compiler, and then you are left with no coding errors but only conceptual errors. Sadly, this is not the case, I compiled something the Nim extension regarded as OK and it broke with a quite obscure error message.
I am sure I did things I am not supposed to do. But Nim was well sold, and I thought I could use it for scientific computing in a functional way. I don't understand what I did wrong nor how to do it right. Could someone explain one or both of these things?
import std/random
import math
from nimPNG import savePNG24
const
SEED = 0
H = 100
W = 100
randomize(SEED) # make reproducible
type
Color = array[3, float]
GrayImg = array[H, array[W, float]]
ColorImg = array[H, array[W, Color]]
Omega = tuple[i: int, j: int]
I_Func = proc (x: Omega): Color {.noSideEffect.}
m_x_Func = proc (x: Omega): float {.noSideEffect.}
h_Func = proc (I: I_func, m_x: m_x_Func, x:Omega): float {.noSideEffect.}
g_Func = proc (I: I_func, m_x: m_x_Func): float {.noSideEffect.}
M_Func = proc (x: Omega): m_x_Func {.noSideEffect.}
F_Func = proc (x: Omega): float {.noSideEffect.}
f_Func = proc (I: I_Func, M: M_Func): F_Func {.noSideEffect.}
proc create_partial_I(img:ColorImg): proc (x: Omega): Color {.noSideEffect.} =
proc I(x: Omega): Color {.noSideEffect.} =
return img[x.i][x.j]
return I
func g_wrapper(h: h_Func): g_Func =
func g(I: I_Func, m_x:m_x_Func): float =
var
num = 0.0
denom = 0.0
for i in 0..H-1:
for j in 0..W-1:
var xp: Omega = (i:i, j:j)
num = num + h(I, m_x, xp) * m_x(xp)
denom = denom + m_x(xp)
return num / denom
return g
func f_wrapper(g: g_Func): f_Func =
func f(I: I_Func, M: M_Func): F_Func =
func F(x: Omega): float =
let m_x = M(x)
return g(I, m_x)
return F
return f
func h_mean(I: I_Func, m_x: m_x_Func, xp: Omega): float =
let color = I(xp)
return sum(color)
const g_mean: g_Func = g_wrapper(h_mean)
func h_var(I: I_Func, m_x:m_x_Func, xp:Omega): float =
let mean_val = g_mean(I, m_x)
var variance: float = 0.0
for i in 0..H-1:
for j in 0..W-1:
for c in 0..2:
variance = (I((i:i, j:j))[c] - mean_val)^2 / (H * W * 3)
return variance
const g_var: g_Func = g_wrapper(h_var)
func get_value_gaussian_M(F: F_Func): M_Func =
func gaussian_M(x: Omega): m_x_Func =
let x_value = F(x)
var weights: GrayImg
for i in 0..H-1:
for j in 0..W-1:
weights[i][j] = exp(-abs(F((i,j)) - x_value))
func m_x(xp: Omega): float =
return weights[xp.i][xp.j]
return m_x
return gaussian_M
func get_spatial_gaussian_M(F: F_Func): M_Func =
func gaussian_M(x: Omega): m_x_Func =
var weights : GrayImg
for i in 0..H-1:
for j in 0..W-1:
let dist_x = float(i - x.i)
let dist_y = float(j - x.j)
weights[i][j] = exp(-sqrt(dist_x^2 + dist_y^2))
func m_x(xp: Omega): float =
return weights[xp.i][xp.j]
return m_x
return gaussian_M
func get_bilateral_M(F: F_Func, coef:float=1): M_Func =
func M(x: Omega): m_x_Func =
let spatial_M = get_spatial_gaussian_M(F)
let value_M = get_value_gaussian_M(F)
func m_x(xp: Omega): float =
return coef * spatial_M(x)(xp) * value_M(x)(xp)
return m_x
return M
proc loop(M_0: M_Func, I: I_Func, f: f_Func): F_Func =
var M = M_0
var F: F_Func
var iterations = 0
while iterations < 10:
var F = f(I, M)
M = get_bilateral_M(F)
return F
proc random_plain_color() : Color =
let R = rand(1.0)
let G = rand(1.0)
let B = rand(1.0)
return [R, G, B]
func add_colors(c1, c2: Color): Color =
var new_color = c1
for ind, c in new_color:
new_color[ind] = c + c2[ind]
return new_color
proc add_noise_to_color(color: Color, sigma: float = 0.1): Color =
let noisy_color: Color = [gauss(0.0, sigma), gauss(0.0, sigma), gauss(0.0, sigma)]
return add_colors(color, noisy_color)
proc create_noisy_color_image(sigma1=0.1, sigma2=0.01, color1 = [1.0, 0, 0.5], color2 = [0.0, 0.5, 1.0]): ColorImg =
var img: ColorImg
for i in 0 .. (H-1):
for j in 0 .. (W div 2):
img[i][j] = add_noise_to_color(color1, sigma1)
for j in (W div 2 + 1) .. (W - 1):
img[i][j] = add_noise_to_color(color2, sigma2)
return img
proc create_noisy_random_color_image(sigma1=0.1, sigma2=0.01): ColorImg =
let color1 = random_plain_color()
let color2 = random_plain_color()
return create_noisy_color_image(sigma1, sigma2, color1, color2)
func image_as_byte_seq(img: ColorImg): seq[uint8] =
const H = img.len
const W = img[0].len
var output_seq: seq[uint8] = @[]
for i in 0..H-1:
for j in 0.. W-1:
for c in 0..2:
let value8b = uint8(round(img[i][j][c] * 255))
output_seq.add(value8b)
return output_seq
proc save_image(img: ColorImg): void =
let byte_seq = image_as_byte_seq(img)
echo savePNG24("out.png", byte_seq, len(img), len(img[0]))
proc create_zeros_F: F_Func =
func F(x: Omega): float =
return 0.0
return F
block main:
let img: ColorImg = create_noisy_random_color_image(sigma1=0.01, sigma2=0.05)
save_image(img)
let I = create_partial_I(img)
let M_0 = get_bilateral_M(create_zeros_F())
let f = f_wrapper(g_mean)
let F = loop(M_0, I, f)
const g_var: g_Func = g_wrapper(h_var)
This line is the problem most likely. You need to use let instead of const (const is for compile time variable; let is for run time immutable variable).
So it become : let g_var: g_Func = g_wrapper(h_var)
The error you encounter is a codegen error : the generated C code by the Nim compiler is invalid and the C compiler gives you an error. That's why it's cryptic : because the error does not come from the Nim compiler but form the C compiler.
THat said your code has other problems : you create global variable (your function pointers g_mean etc..) and you access them in proc that are marked noSideEffect.