Skip to content

Commit 059e1f4

Browse files
authored
Add density_map (#6)
1 parent eea067b commit 059e1f4

File tree

6 files changed

+98
-17
lines changed

6 files changed

+98
-17
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
[![Coverage](https://codecov.io/gh/HolyLab/CounterMarking.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/HolyLab/CounterMarking.jl)
66

77
Utilities for analyzing images from mouse territorial countermarking experiments.
8+
See the [documentation](https://HolyLab.github.io/CounterMarking.jl/stable/) for more information.

docs/src/index.md

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Tips on image quality:
1515

1616
- Put the stimulus near one of the four corners
1717
- Ensure lighting is fairly uniform
18-
- 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)
1918
- Make sure that any extraneous marks (e.g., the black writing in the image above) are of a very different color from scent marks.
19+
- 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
2020

2121
## Tutorial
2222

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

71-
## Processing with the GUI
71+
## Processing data with the GUI
72+
73+
### Running the GUI
7274

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

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

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

116+
### Summarizing the results: create a "density map" of marks across multiple images
117+
118+
If you have many images collected under the same conditions (e.g., with
119+
different subject animals but the same stimuli), you can effectively overlay the
120+
entire collection of images. For this demo, it's assumed that `"results_file_name.jld2"` is one of the files output by the GUI.
121+
122+
```
123+
julia> using CounterMarking, ImageView # load packages (if this is a fresh session)
124+
125+
julia> count = density_map("results_file_name.jld2");
126+
127+
julia> dct = imshow(dmap);
128+
```
129+
130+
!!! tip
131+
`density_map` requires that most or all of your images are of the same size.
132+
114133
## Processing manually
115134

116135
### Step 1: start Julia with the right project
@@ -207,13 +226,3 @@ If you have many images in one folder, you can process them all using a single c
207226
```
208227
julia> process_images("2025-03-15/results.xlsx", "2025-03-15/*.png")
209228
```
210-
211-
### Step 8: create a "density map" of marks across multiple images
212-
213-
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:
214-
215-
```
216-
julia> dmap = density_map("2025-03-15/maleU-*.png");
217-
218-
julia> dct = imshow(dmap);
219-
```

src/CounterMarking.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ using ImageView
1313
using Random
1414

1515
export segment_image, stimulus_index, spots, Spot, upperleft
16-
export writexlsx, process_images
16+
export writexlsx, process_images, density_map
1717
export randshow, meanshow, gui
1818

1919
include("segment.jl")

src/gui.jl

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
1+
"""
2+
gui(outbase, files)
3+
gui(outbase, glob::GlobMatch)
4+
5+
Run the graphical user interface (GUI) for CounterMarking. Supply the base name
6+
of the output files (e.g., "my_results") and a list of image files to process
7+
(alternatively, supply a `glob"pattern"` that matches just the files you want to
8+
process).
9+
10+
The GUI will display the images and their segmentation, and allow the user to
11+
select the segments corresponding to the stimulus (yellow) and marked spots. The
12+
results will be save to:
113
14+
- `outbase.xlsx`: an Excel file with one sheet per image, containing summary
15+
statistics on the stimulus spot and the selected spots.
16+
- `outbase.jld2`: a JLD2 file with one dataset per image, containing the
17+
segmented image, the selected spots, and the stimulus segment.
18+
19+
The JLD2 file can be used by [`density_map`](@ref).
20+
"""
221
function gui(
3-
outbase, files;
22+
outbase::AbstractString, files;
423
colors=distinguishable_colors(15, [RGB(1, 1, 1)]; dropseed=true),
524
btnclick = Condition(), # used for testing
625
whichbutton = Ref{Symbol}(), # used for testing
26+
preclick::Union{Int,Nothing} = nothing, # used for testing
727
)
828
channelpct(x) = string(round(Int, x * 100)) * '%'
929

1030
outbase, _ = splitext(outbase)
1131

32+
# Set up basic properties of the window
1233
winsize = round.(Int, 0.8 .* screen_size())
1334
win = GtkWindow("CounterMarking", winsize...)
1435
ag = Gtk4.GLib.GSimpleActionGroup()
@@ -38,6 +59,7 @@ function gui(
3859
cssprov = GtkCssProvider(css)
3960
push!(Gtk4.display(win), cssprov)
4061

62+
# Create the elements of the GUI
4163
win[] = bx = GtkBox(:v)
4264
ImageView.window_wrefs[win] = nothing
4365
signal_connect(win, :destroy) do w
@@ -87,7 +109,7 @@ function gui(
87109
istim = stimulus_index(seg)
88110
for (j, cb) in enumerate(cbs)
89111
# set_gtk_property!(cb, "active", j <= nsegs)
90-
cb[] = j == istim
112+
cb[] = (j == istim || j == preclick)
91113
end
92114
imshow(canvases[1, 1], img)
93115
imshow(canvases[2, 1], map(i->colors[i], labels_map(seg)))
@@ -135,7 +157,7 @@ function gui(
135157
notify(btnclick) # used in testing
136158
return
137159
end
138-
gui(outbase, glob::Glob.GlobMatch; kwargs...) = gui(outbase, Glob.glob(glob); kwargs...)
160+
gui(outbase::AbstractString, glob::Glob.GlobMatch; kwargs...) = gui(outbase, Glob.glob(glob); kwargs...)
139161

140162
function linkpair(img, imgc)
141163
zr, slicedata = roi(img)

src/segment.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,48 @@ end
148148
# end
149149
# return img
150150
# end
151+
152+
"""
153+
nmarked = density_map(jldfile::AbstractString)
154+
155+
Given a JLD2 file `jldfile` written by `gui`, return an array `nmarked` counting
156+
of the number of images with a urine spot in each pixel. Before counting, the
157+
images are flipped so that the stimulus segment is in the upper left corner.
158+
"""
159+
function density_map(jldfile::AbstractString)
160+
data = load(jldfile)
161+
fns, imgsizes = String[], Tuple{Int, Int}[]
162+
for (filename, (seg, _, _)) in data
163+
push!(fns, filename)
164+
imgsize = size(seg)
165+
push!(imgsizes, imgsize)
166+
end
167+
szcount = Dict{Tuple{Int, Int}, Int}()
168+
for sz in imgsizes
169+
szcount[sz] = get(szcount, sz, 0) + 1
170+
end
171+
imgsize, n = argmax(last, szcount)
172+
badfiles = fns[imgsizes .!= Ref(imgsize)]
173+
if n != length(data)
174+
if n == 1
175+
error("no dominant image size found in $jldfile")
176+
else
177+
@warn("Image sizes do not all match, skipping $badfiles")
178+
end
179+
end
180+
181+
nmarked = zeros(Int, imgsize)
182+
midpoint = imgsize 2
183+
for (fn, (seg, _, stimulus)) in data
184+
fn badfiles && continue
185+
sidx, ss = stimulus
186+
if ss.centroid[1] > midpoint[1]
187+
seg = reverse(seg; dims=1)
188+
end
189+
if ss.centroid[2] > midpoint[2]
190+
seg = reverse(seg; dims=2)
191+
end
192+
nmarked .+= seg .∉ Ref((0, sidx))
193+
end
194+
return nmarked
195+
end

test/runtests.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,15 @@ using Test
4545
rm(tmpfile, force=true)
4646
btnclick = Condition()
4747
whichbutton = Ref{Symbol}()
48-
@async gui(tmpfile, [joinpath(testdir, "Picture.png")]; btnclick, whichbutton)
48+
@async gui(tmpfile, [joinpath(testdir, "Picture.png")]; btnclick, whichbutton, preclick=3)
4949
sleep(5)
5050
whichbutton[] = :done
5151
notify(btnclick)
5252
wait(btnclick)
5353
@test isfile(tmpfile)
5454
@test isfile(splitext(tmpfile)[1] * ".jld2")
55+
56+
# Test the density map
57+
count = density_map(splitext(tmpfile)[1] * ".jld2")
58+
@test extrema(count) == (0, 1)
5559
end

0 commit comments

Comments
 (0)