Skip to content

Commit a8066c5

Browse files
committed
Update examples to use smld_model for image generation and clarify usage notes
1 parent 92011e1 commit a8066c5

File tree

3 files changed

+106
-187
lines changed

3 files changed

+106
-187
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ using MicroscopePSFs # Needed for PSF types
107107

108108
# Generate images from diffusion simulation output
109109
psf = GaussianPSF(0.15) # 150nm PSF width
110+
# Use smld_model to avoid double-counting localization errors
110111
images = gen_images(smld, psf;
111112
frame_integration=10, # 10 simulation time steps for each camera frame
112113
support=1.0 # PSF support range

docs/src/diffusion/examples.md

Lines changed: 100 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ CurrentModule = SMLMSim
66

77
This page provides complete examples for using the diffusion-interaction simulation capabilities of SMLMSim.
88

9+
## Working with Trajectories
10+
11+
SMLMSim provides several utility functions for working with trajectories:
12+
13+
- `get_track(smld, id)`: Returns a new SMLD containing only emitters with the specified track_id
14+
- `get_num_tracks(smld)`: Returns the number of unique tracks in an SMLD
15+
- `get_tracks(smld)`: Returns a vector of SMDLs, one for each unique track
16+
17+
These functions are useful for analyzing and visualizing trajectory data, as demonstrated in the examples below.
18+
919
## Basic Diffusion Simulation
1020

1121
This example demonstrates how to run a basic diffusion simulation and visualize the results:
@@ -32,9 +42,13 @@ params = DiffusionSMLMParams(
3242
# Run simulation
3343
smld = simulate(params; photons=1000.0)
3444
35-
# Extract coordinates from different frames
36-
function extract_frame(smld, frame_num)
37-
# Filter emitters for this frame
45+
# Extract coordinates based on monomer/dimer state for a specific frame
46+
function extract_frame_by_state(smld, frame_num)
47+
# This approach doesn't use our new utility functions directly yet,
48+
# but demonstrates what would be useful additions in the future
49+
# (e.g., get_frame and filter_by_state functions)
50+
51+
# Filter emitters for this frame the traditional way
3852
frame_emitters = filter(e -> e.frame == frame_num, smld.emitters)
3953
4054
# Extract coordinates and states
@@ -68,7 +82,7 @@ for (i, frame) in enumerate(frames_to_show)
6882
)
6983
7084
# Extract and plot data
71-
x, y, colors = extract_frame(smld, frame)
85+
x, y, colors = extract_frame_by_state(smld, frame)
7286
scatter!(ax, x, y, color=colors, markersize=6)
7387
7488
# Set consistent axis limits
@@ -89,7 +103,6 @@ fig
89103
90104
```
91105
![Basic Diffusion Simulation](diffusion_basic_simulation.png)
92-
```
93106

94107
## Frame Integration for Time-Lapse Imaging
95108

@@ -129,6 +142,8 @@ psf = MicroscopePSFs.GaussianPSF(0.15) # 150nm PSF width
129142
# Generate microscope images with frame integration
130143
# The frame_integration parameter determines how many simulation time
131144
# points are integrated into each output frame
145+
# Note: For diffusion simulations, the smld already contains correct positions without
146+
# localization uncertainty, so we can use it directly for generating camera images
132147
images = gen_images(smld, psf;
133148
bg=5.0, # background photons per pixel
134149
frame_integration=10, # integrate 10 simulation steps per frame
@@ -144,7 +159,7 @@ ax = Axis(fig[1, 1],
144159
)
145160
146161
# Select a frame to display
147-
frame_to_show = 15
162+
frame_to_show = 3 # Changed from 15 to a valid frame index (between 1 and 6)
148163
heatmap!(ax, transpose(images[:, :, frame_to_show]), colormap=:inferno)
149164
150165
save("diffusion_frame_integration.png", fig)
@@ -153,7 +168,6 @@ fig
153168
154169
```
155170
![Frame Integration for Time-Lapse Imaging](diffusion_frame_integration.png)
156-
```
157171

158172
The `frame_integration` parameter is crucial for realistic diffusion imaging:
159173

@@ -220,7 +234,6 @@ fig
220234
221235
```
222236
![Analyzing Dimer Formation](diffusion_dimer_formation.png)
223-
```
224237

225238
## Generating Microscope Images
226239

@@ -310,213 +323,115 @@ fig
310323
311324
```
312325
![Generating Microscope Images](diffusion_microscope_images.png)
313-
```
314326

315-
## Diffusion with Different Boundary Conditions
327+
## Two Interacting Particles
316328

317-
This example compares periodic and reflecting boundary conditions:
329+
This example shows two particles interacting in a small box with reflecting boundary conditions:
318330

319331
```@example
320332
using SMLMSim
321333
using CairoMakie
322334
323-
# Set up simulations with different boundary conditions
324-
params_periodic = DiffusionSMLMParams(
325-
density = 0.3, # molecules per μm²
326-
box_size = 10.0, # μm
327-
diff_monomer = 0.2, # μm²/s (faster diffusion)
328-
t_max = 10.0, # s
329-
boundary = "periodic" # Periodic boundaries
330-
)
331-
332-
params_reflecting = DiffusionSMLMParams(
333-
density = 0.3, # molecules per μm²
334-
box_size = 10.0, # μm
335-
diff_monomer = 0.2, # μm²/s (faster diffusion)
336-
t_max = 10.0, # s
337-
boundary = "reflecting" # Reflecting boundaries
335+
# Set up a minimal simulation with just two particles
336+
params = DiffusionSMLMParams(
337+
density = 2.0, # 2 particles in a 1×1 μm box
338+
box_size = 1.0, # 1 μm box for close interactions
339+
diff_monomer = 0.1, # μm²/s
340+
diff_dimer = 0.05, # μm²/s
341+
k_off = 0.5, # s⁻¹ (moderate dimer stability)
342+
r_react = 0.05, # μm (large reaction radius for demonstration)
343+
d_dimer = 0.07, # μm (dimer separation)
344+
dt = 0.01, # s
345+
t_max = 5.0, # s
346+
boundary = "reflecting", # Reflecting boundaries
347+
camera_framerate = 10.0 # fps
338348
)
339349
340-
# Run simulations
341-
smld_periodic = simulate(params_periodic)
342-
smld_reflecting = simulate(params_reflecting)
343-
344-
# Function to extract trajectories
345-
function extract_trajectories(smld)
346-
# Group emitters by ID
347-
emitters_by_id = Dict()
348-
for e in smld.emitters
349-
if !haskey(emitters_by_id, e.id)
350-
emitters_by_id[e.id] = []
351-
end
352-
push!(emitters_by_id[e.id], e)
353-
end
354-
355-
# Convert to trajectories
356-
trajectories = []
357-
for (id, emitters) in emitters_by_id
358-
# Sort by timestamp
359-
sort!(emitters, by = e -> e.timestamp)
360-
361-
# Extract coordinates
362-
times = [e.timestamp for e in emitters]
363-
x = [e.x for e in emitters]
364-
y = [e.y for e in emitters]
365-
366-
push!(trajectories, (id=id, times=times, x=x, y=y))
367-
end
368-
369-
return trajectories
370-
end
350+
# Run simulation - override density to get exactly 2 particles
351+
smld = simulate(params; override_count=2, photons=1000.0)
371352
372-
# Get trajectories
373-
traj_periodic = extract_trajectories(smld_periodic)
374-
traj_reflecting = extract_trajectories(smld_reflecting)
353+
# Get trajectories using the built-in function
354+
track_smlds = get_tracks(smld)
375355
376-
# Visualize trajectories
377-
function plot_trajectories(trajectories, title, box_size)
378-
fig = Figure(size=(700, 600))
379-
380-
ax = Axis(fig[1, 1],
381-
title=title,
382-
xlabel="x (μm)",
383-
ylabel="y (μm)",
384-
aspect=DataAspect()
385-
)
386-
387-
# Plot first 20 trajectories
388-
for i in 1:min(20, length(trajectories))
389-
traj = trajectories[i]
390-
lines!(ax, traj.x, traj.y, color=Cycled(i), linewidth=1.5, alpha=0.7)
391-
392-
# Mark start position
393-
scatter!(ax, [traj.x[1]], [traj.y[1]], color=Cycled(i), marker=:circle,
394-
markersize=8)
395-
396-
# Mark end position
397-
scatter!(ax, [traj.x[end]], [traj.y[end]], color=Cycled(i), marker=:star,
398-
markersize=10)
399-
end
356+
# Convert to the format needed for plotting
357+
trajectories = []
358+
for track_smld in track_smlds
359+
# Get ID from first emitter
360+
id = track_smld.emitters[1].id
400361
401-
# Show box boundaries
402-
box = [0 0; box_size 0; box_size box_size; 0 box_size; 0 0]
403-
lines!(ax, box[:, 1], box[:, 2], color=:black, linewidth=2)
362+
# Sort by timestamp
363+
sort!(track_smld.emitters, by = e -> e.timestamp)
404364
405-
# Set axis limits with some padding
406-
limits!(ax, -0.5, box_size+0.5, -0.5, box_size+0.5)
365+
# Extract coordinates and state
366+
times = [e.timestamp for e in track_smld.emitters]
367+
x = [e.x for e in track_smld.emitters]
368+
y = [e.y for e in track_smld.emitters]
369+
states = [e.state for e in track_smld.emitters]
407370
408-
return fig
371+
push!(trajectories, (id=id, times=times, x=x, y=y, states=states))
409372
end
410373
411-
# Plot both types of trajectories
412-
fig_periodic = plot_trajectories(traj_periodic, "Periodic Boundaries",
413-
params_periodic.box_size)
414-
fig_reflecting = plot_trajectories(traj_reflecting, "Reflecting Boundaries",
415-
params_reflecting.box_size)
416-
417-
save("diffusion_periodic_boundaries.png", fig_periodic)
418-
save("diffusion_reflecting_boundaries.png", fig_reflecting)
419-
(fig_periodic, fig_reflecting)
420-
# output
421-
422-
```
423-
![Diffusion with Periodic Boundaries](diffusion_periodic_boundaries.png)
424-
![Diffusion with Reflecting Boundaries](diffusion_reflecting_boundaries.png)
425-
```
426-
427-
## Long-term Evolution of Dimer Population
428-
429-
This example simulates the long-term evolution of dimer formation under different conditions:
430-
431-
```@example
432-
using SMLMSim
433-
using CairoMakie
374+
# Visualize interaction dynamics
375+
fig = Figure(size=(700, 600))
434376
435-
# Set up parameters for longer simulation
436-
params = DiffusionSMLMParams(
437-
density = 0.5, # molecules per μm²
438-
box_size = 20.0, # μm
439-
diff_monomer = 0.1, # μm²/s
440-
diff_dimer = 0.05, # μm²/s
441-
k_off = 0.1, # s⁻¹
442-
r_react = 0.01, # μm
443-
d_dimer = 0.05, # μm
444-
dt = 0.01, # s
445-
t_max = 60.0, # s (longer simulation)
446-
camera_framerate = 5.0 # fps (slower framerate for long simulation)
377+
ax = Axis(fig[1, 1],
378+
title="Two Particles in 1μm Box (Reflecting Boundaries)",
379+
xlabel="x (μm)",
380+
ylabel="y (μm)",
381+
aspect=DataAspect()
447382
)
448383
449-
# Run simulation
450-
smld = simulate(params)
451-
452-
# Calculate dimer fraction over time
453-
frames, dimer_fractions = analyze_dimer_fraction(smld)
454-
455-
# Convert frames to time
456-
time = (frames .- 1) ./ params.camera_framerate
457-
458-
# Calculate theoretical equilibrium dimer fraction
459-
# For simple second-order kinetics with monomer-monomer association
460-
# and first-order dimer dissociation
461-
function theoretical_dimer_fraction(t, k_on, k_off, initial_concentration)
462-
# k_on is in μm²/s units
463-
# k_off is in s⁻¹ units
464-
# initial_concentration is in molecules/μm² units
465-
466-
# Equilibrium constant (dimensionless)
467-
K_eq = k_on / k_off
384+
# Plot trajectories with state-dependent coloring
385+
for (i, traj) in enumerate(trajectories)
386+
# Create segments with colors based on state
387+
segments_x = []
388+
segments_y = []
389+
colors = []
468390
469-
# Total concentration (constant)
470-
c_total = initial_concentration
391+
for j in 1:(length(traj.times)-1)
392+
push!(segments_x, [traj.x[j], traj.x[j+1]])
393+
push!(segments_y, [traj.y[j], traj.y[j+1]])
394+
push!(colors, traj.states[j] == :monomer ? :blue : :red)
395+
end
471396
472-
# Equilibrium dimer fraction
473-
f_eq = (1 + 4*K_eq*c_total - sqrt(1 + 8*K_eq*c_total)) / (4*K_eq*c_total)
397+
# Plot each segment with appropriate color
398+
for j in 1:length(segments_x)
399+
lines!(ax, segments_x[j], segments_y[j],
400+
color=colors[j], linewidth=2,
401+
label=j==1 ? "Particle $(traj.id)" : nothing)
402+
end
474403
475-
# Time-dependent approach to equilibrium
476-
# This is a simplified model assuming well-mixed conditions
477-
τ = 1 / (k_off + k_on * c_total)
478-
f_t = f_eq * (1 - exp(-t/τ))
404+
# Mark starting position
405+
scatter!(ax, [traj.x[1]], [traj.y[1]],
406+
color=:black, marker=:circle, markersize=10)
479407
480-
return f_t
408+
# Mark ending position
409+
scatter!(ax, [traj.x[end]], [traj.y[end]],
410+
color=:black, marker=:star, markersize=12)
481411
end
482412
483-
# Estimate k_on from r_react and diffusion
484-
k_on = 2π * params.diff_monomer * params.r_react
485-
k_off = params.k_off
486-
c_total = params.density
413+
# Show box boundaries
414+
box = [0 0; 1 0; 1 1; 0 1; 0 0]
415+
lines!(ax, box[:, 1], box[:, 2], color=:black, linewidth=2)
487416
488-
# Calculate theoretical curve
489-
t_theory = LinRange(0, maximum(time), 1000)
490-
f_theory = theoretical_dimer_fraction.(t_theory, k_on, k_off, c_total)
417+
# Add legend for state colors
418+
legend_elements = [
419+
LineElement(color=:blue, linewidth=3),
420+
LineElement(color=:red, linewidth=3),
421+
MarkerElement(color=:black, marker=:circle, markersize=8),
422+
MarkerElement(color=:black, marker=:star, markersize=10)
423+
]
424+
legend_labels = ["Monomer", "Dimer", "Start", "End"]
491425
492-
# Visualize results
493-
fig = Figure(size=(800, 500))
426+
Legend(fig[1, 2], legend_elements, legend_labels, "States")
494427
495-
ax = Axis(fig[1, 1],
496-
title="Dimer Formation Dynamics",
497-
xlabel="Time (s)",
498-
ylabel="Fraction of molecules in dimers"
499-
)
500-
501-
# Plot simulation results
502-
scatter!(ax, time, dimer_fractions, color=:blue, markersize=6,
503-
label="Simulation")
504-
505-
# Plot theoretical curve
506-
lines!(ax, t_theory, f_theory, color=:red, linewidth=2,
507-
label="Theoretical model")
428+
# Set axis limits with some padding
429+
limits!(ax, -0.05, 1.05, -0.05, 1.05)
508430
509-
# Add equilibrium line
510-
hlines!(ax, theoretical_dimer_fraction(Inf, k_on, k_off, c_total),
511-
color=:black, linestyle=:dash, linewidth=1,
512-
label="Equilibrium")
513-
514-
axislegend(ax)
515-
516-
save("diffusion_long_term_evolution.png", fig)
431+
save("diffusion_two_particles.png", fig)
517432
fig
518433
# output
519434
520435
```
521-
![Long-term Evolution of Dimer Population](diffusion_long_term_evolution.png)
522-
```
436+
![Two Interacting Particles](diffusion_two_particles.png)
437+

0 commit comments

Comments
 (0)