5

I am new to Julia, and I am trying to understand basic data visualization. I am creating a 2D array of noise with:

xRange, yRange = 1:1:300, 1:1:300
maxVal = 0.3
noiseArr = rand(length(xRange),length(yRange))*maxVal

The resulting array is shown below (left). I would like to identify specific pixels -- defined by a rectangle with length, width, and rotation -- and set those values to a known number. Ultimately, I would like something like the image shown below (right).

I've no package preferences, but I've been looking at Images, OpenCV, etc. I'm hoping there's a straight-forward way to do this.

enter image description here

AaronJPung
  • 1,105
  • 1
  • 19
  • 35
  • You might just write a [line-drawing algorithm](https://en.wikipedia.org/wiki/Line_drawing_algorithm) yourself (they're not difficult) and fill in the interior. – phipsgabler Sep 25 '20 at 06:33
  • maybe that will help you https://www.youtube.com/watch?v=jj8Iy7FFMeM – woblob Sep 25 '20 at 09:32
  • @phipsgabler, thank you for the suggestion! I briefly considered this during my initial search, then thought that Julia certainly must have a faster, out-of-the-box function that allows people to make lines easily. Do you know of a package that can just draw a line in the above noise array? – AaronJPung Sep 26 '20 at 01:18

2 Answers2

3

Here is a quick example of drawing it using primitives with Luxor.jl

using Luxor

function b()
    Drawing(300, 300, "hello-world.png")
    background("black")
    sethue("white")
    #Luxor.scale(1,1)
    Luxor.translate(150, 30)
    Luxor.rotate(10 * pi / 180)
    w = 40
    h = 200
    rect(O, w, h, :fill)
    finish()
    preview()
end

rotated_rectangle_on_black

Now, instead of drawing it directly with Luxor you can extract the transformation matrix and use it somewhere else.

using Plots
using GR
using LinearAlgebra

gr(size = (300, 300), legend = false)

function a(transform=Matrix{Int}(I, 3, 3))
    side = 300
    width = side
    height = side
    xs = [string("x", i) for i = 1:width]
    ys = [string("y", i) for i = 1:height]
    z = float((1:height) * reshape(1:width, 1, :))
    # Plots.heatmap(xs, ys, z, aspect_ratio = 1)

    white = maximum(z)

    # Draw a rectangle with a rotation matrix applied
    for x in 0:40
        for y in 0:200
            t = transform*[x;y;1]
            z[round(Int, t[2]), round(Int, t[1])] = white
        end
    end

    Plots.heatmap(xs, ys, z, aspect_ratio = 1)
end

using Luxor

function b()
    Drawing(300, 300, "hello-world.png")
    background("black")
    sethue("white")
    #Luxor.scale(1,1)
    Luxor.translate(100, 60)
    Luxor.rotate(-10 * pi / 180)
    w = 40
    h = 200
    rect(O, w, h, :fill)
    finish()
    preview()

    tranformation_matrix = Luxor.cairotojuliamatrix(Luxor.getmatrix())

    a(tranformation_matrix)
end

Note this leaves stray pixels, because my for loop isn't efficient at rasterizing the fill. Accessing pixel data out of Luxor may be better, or using some other function that applies affine transformations to a matrix.

rotated_rectangle_on_heatmap

Caution about plots in julia:

Time to first plot is slow. And to do any real iterations on it, you should leverage Revise.jl and save your test functions in a file and include it with includet("test.jl"). Then your julia session is persistent and you only have to wait for your using statements once.

phyatt
  • 18,472
  • 5
  • 61
  • 80
2

I would like to identify specific pixels -- defined by a rectangle with length, width, and rotation

Knowing length, width and rotation is not enough to uniquely position a rectangle on a plane. You also need translation.

Here is a simple code that gives you an example what you can do (it is not super efficient, but is fast enough for demo purposes):

function rectangle!(x1, x2, x3, noiseArr, val)
    A = [x2-x1 x3-x1]
    b = x1
    iA = inv(A)

    for i in axes(noiseArr, 1), j in axes(noiseArr, 2)
        it, jt = iA * ([i, j]-b)
        0 <= it <= 1 && 0 <= jt <= 1 && (noiseArr[i, j] = val)
    end
end

x1 = [40, 140]
x2 = [230, 100]
x3 = [50, 170]
rectangle!(x1, x2, x3, noiseArr, 0.0)

In it - instead of passing a rotation, scaling and translation I thought it is easier to assume you pass three vertices of the rectangle x1, x2, and x3 (assuming that its sides are formed by x1-x2 and x1-x3 pairs) and it calculates the affine transformation you need. val is the value the pixels inside of the rectangle should get.

Note that essentially what we do is computing a reverse affine transformation of a rectangle back to a unit square and check which points lie within a unit square (which is easy).

(as commenters noted above - this is not an optimal way to do it as you do a lot of computations that are not needed, but I hope that it has the benefit of being easier to understand; also the benefit of Julia is that writing of such a loop is not a problem and it still runs fast enough)

Bogumił Kamiński
  • 66,844
  • 3
  • 80
  • 107
  • Argh, the new highlight.js thing doesn't support Julia. That code is detected as YAML! – phipsgabler Sep 25 '20 at 10:36
  • 1
    This is precisely what I was looking for, and consolidated enough to be done in a single, straight-forward function. Thank you! – AaronJPung Sep 28 '20 at 15:59
  • I am glad to have helped. I hope the linear algebra behind is clear - we transform points `[0,0]`, `[1,0]` and `[0,1]` into `x1`, `x2`, `x3` using `Ax+b` function. – Bogumił Kamiński Sep 28 '20 at 16:45