Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
[![Coverage](https://codecov.io/gh/HolyLab/CounterMarking.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/HolyLab/CounterMarking.jl)

Utilities for analyzing images from mouse territorial countermarking experiments.
See the [documentation](https://HolyLab.github.io/CounterMarking.jl/stable/) for more information.
33 changes: 21 additions & 12 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Tips on image quality:

- Put the stimulus near one of the four corners
- Ensure lighting is fairly uniform
- Try to ensure the entire image is of the filter paper, and that there aren't any black edges to the image (see lower left corner in the example above)
- Make sure that any extraneous marks (e.g., the black writing in the image above) are of a very different color from scent marks.
- Ensure that all your images are of the same size (i.e., same number of pixels horizontally and vertically), even if there are some extra pixels on the edges of the image

## Tutorial

Expand Down Expand Up @@ -68,7 +68,9 @@ and it will automatically "activate" this project and you'll have access to all
[Pkg documentation](https://pkgdocs.julialang.org/v1/getting-started/) for
more information.

## Processing with the GUI
## Processing data with the GUI

### Running the GUI

From within `MyCounterMarkingFolder` created above, start Julia like this:

Expand Down Expand Up @@ -111,6 +113,23 @@ If you like the segmentation, your tasks are:

After it finishes cycling through all the images, it will save your results and close the window.

### Summarizing the results: create a "density map" of marks across multiple images

If you have many images collected under the same conditions (e.g., with
different subject animals but the same stimuli), you can effectively overlay the
entire collection of images. For this demo, it's assumed that `"results_file_name.jld2"` is one of the files output by the GUI.

```
julia> using CounterMarking, ImageView # load packages (if this is a fresh session)

julia> count = density_map("results_file_name.jld2");

julia> dct = imshow(dmap);
```

!!! tip
`density_map` requires that most or all of your images are of the same size.

## Processing manually

### Step 1: start Julia with the right project
Expand Down Expand Up @@ -207,13 +226,3 @@ If you have many images in one folder, you can process them all using a single c
```
julia> process_images("2025-03-15/results.xlsx", "2025-03-15/*.png")
```

### Step 8: create a "density map" of marks across multiple images

If you have many images collected under the same conditions (e.g., with different subject animals but the same stimuli), you can effectively overlay the entire collection of images:

```
julia> dmap = density_map("2025-03-15/maleU-*.png");

julia> dct = imshow(dmap);
```
2 changes: 1 addition & 1 deletion src/CounterMarking.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ using ImageView
using Random

export segment_image, stimulus_index, spots, Spot, upperleft
export writexlsx, process_images
export writexlsx, process_images, density_map
export randshow, meanshow, gui

include("segment.jl")
Expand Down
28 changes: 25 additions & 3 deletions src/gui.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
"""
gui(outbase, files)
gui(outbase, glob::GlobMatch)

Run the graphical user interface (GUI) for CounterMarking. Supply the base name
of the output files (e.g., "my_results") and a list of image files to process
(alternatively, supply a `glob"pattern"` that matches just the files you want to
process).

The GUI will display the images and their segmentation, and allow the user to
select the segments corresponding to the stimulus (yellow) and marked spots. The
results will be save to:

- `outbase.xlsx`: an Excel file with one sheet per image, containing summary
statistics on the stimulus spot and the selected spots.
- `outbase.jld2`: a JLD2 file with one dataset per image, containing the
segmented image, the selected spots, and the stimulus segment.

The JLD2 file can be used by [`density_map`](@ref).
"""
function gui(
outbase, files;
outbase::AbstractString, files;
colors=distinguishable_colors(15, [RGB(1, 1, 1)]; dropseed=true),
btnclick = Condition(), # used for testing
whichbutton = Ref{Symbol}(), # used for testing
preclick::Union{Int,Nothing} = nothing, # used for testing
)
channelpct(x) = string(round(Int, x * 100)) * '%'

outbase, _ = splitext(outbase)

# Set up basic properties of the window
winsize = round.(Int, 0.8 .* screen_size())
win = GtkWindow("CounterMarking", winsize...)
ag = Gtk4.GLib.GSimpleActionGroup()
Expand Down Expand Up @@ -38,6 +59,7 @@
cssprov = GtkCssProvider(css)
push!(Gtk4.display(win), cssprov)

# Create the elements of the GUI
win[] = bx = GtkBox(:v)
ImageView.window_wrefs[win] = nothing
signal_connect(win, :destroy) do w
Expand Down Expand Up @@ -87,7 +109,7 @@
istim = stimulus_index(seg)
for (j, cb) in enumerate(cbs)
# set_gtk_property!(cb, "active", j <= nsegs)
cb[] = j == istim
cb[] = (j == istim || j == preclick)
end
imshow(canvases[1, 1], img)
imshow(canvases[2, 1], map(i->colors[i], labels_map(seg)))
Expand Down Expand Up @@ -135,7 +157,7 @@
notify(btnclick) # used in testing
return
end
gui(outbase, glob::Glob.GlobMatch; kwargs...) = gui(outbase, Glob.glob(glob); kwargs...)
gui(outbase::AbstractString, glob::Glob.GlobMatch; kwargs...) = gui(outbase, Glob.glob(glob); kwargs...)

Check warning on line 160 in src/gui.jl

View check run for this annotation

Codecov / codecov/patch

src/gui.jl#L160

Added line #L160 was not covered by tests

function linkpair(img, imgc)
zr, slicedata = roi(img)
Expand Down
45 changes: 45 additions & 0 deletions src/segment.jl
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,48 @@
# end
# return img
# end

"""
nmarked = density_map(jldfile::AbstractString)

Given a JLD2 file `jldfile` written by `gui`, return an array `nmarked` counting
of the number of images with a urine spot in each pixel. Before counting, the
images are flipped so that the stimulus segment is in the upper left corner.
"""
function density_map(jldfile::AbstractString)
data = load(jldfile)
fns, imgsizes = String[], Tuple{Int, Int}[]
for (filename, (seg, _, _)) in data
push!(fns, filename)
imgsize = size(seg)
push!(imgsizes, imgsize)
end
szcount = Dict{Tuple{Int, Int}, Int}()
for sz in imgsizes
szcount[sz] = get(szcount, sz, 0) + 1
end
imgsize, n = argmax(last, szcount)
badfiles = fns[imgsizes .!= Ref(imgsize)]
if n != length(data)
if n == 1
error("no dominant image size found in $jldfile")

Check warning on line 175 in src/segment.jl

View check run for this annotation

Codecov / codecov/patch

src/segment.jl#L174-L175

Added lines #L174 - L175 were not covered by tests
else
@warn("Image sizes do not all match, skipping $badfiles")

Check warning on line 177 in src/segment.jl

View check run for this annotation

Codecov / codecov/patch

src/segment.jl#L177

Added line #L177 was not covered by tests
end
end

nmarked = zeros(Int, imgsize)
midpoint = imgsize .÷ 2
for (fn, (seg, _, stimulus)) in data
fn ∈ badfiles && continue
sidx, ss = stimulus
if ss.centroid[1] > midpoint[1]
seg = reverse(seg; dims=1)

Check warning on line 187 in src/segment.jl

View check run for this annotation

Codecov / codecov/patch

src/segment.jl#L187

Added line #L187 was not covered by tests
end
if ss.centroid[2] > midpoint[2]
seg = reverse(seg; dims=2)
end
nmarked .+= seg .∉ Ref((0, sidx))
end
return nmarked
end
6 changes: 5 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,15 @@ using Test
rm(tmpfile, force=true)
btnclick = Condition()
whichbutton = Ref{Symbol}()
@async gui(tmpfile, [joinpath(testdir, "Picture.png")]; btnclick, whichbutton)
@async gui(tmpfile, [joinpath(testdir, "Picture.png")]; btnclick, whichbutton, preclick=3)
sleep(5)
whichbutton[] = :done
notify(btnclick)
wait(btnclick)
@test isfile(tmpfile)
@test isfile(splitext(tmpfile)[1] * ".jld2")

# Test the density map
count = density_map(splitext(tmpfile)[1] * ".jld2")
@test extrema(count) == (0, 1)
end
Loading