Interactively cropping a TIFF stack?

I’ve got some 8-bit TIFF stacks, on the order of 300x300x150 (x * y * slices) in size. I can load these into Julia fine and display them, what I’d really like to be able to do now is interactively crop out a region define by the mouse (rectangular is fine, no need for spline fits to many points) and save that crop to a new image.

The basic code I’ve got so far is:

using Images, ImageView;
using Gtk;

using QuartzImageIO; 
#On a mac, it seems I need this else I get errors about no being able to load image

filename = open_dialog("My Open dialog")
image = load(filename);

imshow(image);

mouse_pos = map(d->println("x = $(float(d.position.x)), y = $(float(d.position.y))"),s["gui"]["canvas"].mouse.buttonpress)
@show mouse_pos;

I’ll admit the mouse_pos bit is complete cargo cult programming, it came from a google search and I’m not sure how it is supposed to work. It just doesn’t seem to for me. I think I could probably do this using GTKReactive, but I don’t really want t create a whole GUI program for this simple-ish task.

Any pointers to info or help with the code is greatly appreciated.

Here’s a solution with GLMakie, although somehow it seems the zoom rectangle was broken in the last version so it’s not actually visible. Would have been cooler if the selection was actually visible.

I didn’t write the cropping and saving part but you can see where you’d put it in the code.

using GLMakie

images = randn(300, 300, 150)

fig = Figure()

i_slice = Node(1)

ax = Axis(fig[1, 1], aspect = DataAspect(), yreversed = true,
    title = @lift("Slice $($i_slice)"))
hidedecorations!(ax)

chosen_slice = @lift(images[:, :, $i_slice]')

i = image!(ax, chosen_slice)
translate!(i, 0, 0, -5)

buttongrid = fig[1, 2] = GridLayout(tellheight = false)
b = Button(buttongrid[1, 1], label = "Save crop")
b2 = Button(buttongrid[2, 1], label = "Next image")
b3 = Button(buttongrid[3, 1], label = "Previous image")

on(b.clicks) do c
    lims = ax.finallimits[]
    println("save image cropped to $lims")
end

on(b2.clicks) do c
    i_slice[] = mod1(i_slice[] + 1, 150)
    autolimits!(ax)
end

on(b3.clicks) do c
    i_slice[] = mod1(i_slice[] - 1, 150)
    autolimits!(ax)
end

fig
1 Like

Thank you!

It is a shame the crop boundary isn;t drawn, but that’s something minor for now.

Hi everyone,
I’m digging up this topic just to know if someone could help me to save the cropped images with Jules’s solution?
I modified the code as above, but i must be missing something as it always returns an error when I try to save the cropped version of my image.

Thank you for your help,
Josselin

using GLMakie
using FileIO
using ImageIO

input = load("/path/to/input.png")
images = input
images
fig = Figure()

i_slice = Observable(1)

ax = Axis(fig[1, 1], aspect = DataAspect(), yreversed = true,
    title = @lift("Slice $($i_slice)"))
hidedecorations!(ax)

chosen_slice = @lift(images[:, :, $i_slice]')

i = image!(ax, chosen_slice)
translate!(i, 0, 0, -5)

buttongrid = fig[1, 2] = GridLayout(tellheight = false)
b = Button(buttongrid[1, 1], label = "Save crop")
b2 = Button(buttongrid[2, 1], label = "Next image")
b3 = Button(buttongrid[3, 1], label = "Previous image")

on(b.clicks) do c
    lims = ax.finallimits[]
    println("save image cropped to $lims")
    # I tried to save $lims, $i, $ax... 
    save("ouput.png", lims)
end

on(b2.clicks) do c
    i_slice[] = mod1(i_slice[] + 1, 150)
    autolimits!(ax)
end

on(b3.clicks) do c
    i_slice[] = mod1(i_slice[] - 1, 150)
    autolimits!(ax)
end

fig

what’s the error?

Hi Jules,
With save("ouput.png", lims) I have the following error message :

Errors encountered while save  File{DataFormat{:PNG}, String}("ouput.png").
All errors:
=============
MethodError: no method matching save(::File{DataFormat{:PNG}, String}, ::GeometryBasics.HyperRectangle{2, Float32})

I think that I’m not saving the good object ?!

Best,
Josselin

You would ideally use the limits as guidelines to figure out how to actually crop + save the image, so for example:

on(b.clicks) do c
    lims = ax.finallimits[]
    println("save image cropped to $lims")
    # I tried to save $lims, $i, $ax... 
    mini, maxi = extrema(lims) # get the bottom left and top right corners of `lims`
    mini = round.(Int, mini) # to index into an image, which is an array, we need ints
    maxi = round.(Int, maxi) # and the axis bounding box is always represented in floats
    cropped_slice = chosen_slice[][mini[1]:maxi[1], mini[2]:maxi[2]]
    save("output_$(i_slice[]).png", cropped_slice)
end

or something like this. Basically, you’re saving a cropped version of chosen_slice[] because that is what is actually displayed on the screen, and you know the number of the image from i_slice[]. I hope it makes sense!

1 Like

@asinghvi17 Thank you very much! It make sens with your explanation!
Josselin