@@ -6,6 +6,16 @@ CurrentModule = SMLMSim
66
77This 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
1121This example demonstrates how to run a basic diffusion simulation and visualize the results:
@@ -32,9 +42,13 @@ params = DiffusionSMLMParams(
3242# Run simulation
3343smld = 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
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
132147images = 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)
148163heatmap!(ax, transpose(images[:, :, frame_to_show]), colormap=:inferno)
149164
150165save("diffusion_frame_integration.png", fig)
153168
154169```
155170![ Frame Integration for Time-Lapse Imaging] ( diffusion_frame_integration.png )
156- ```
157171
158172The ` frame_integration ` parameter is crucial for realistic diffusion imaging:
159173
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
320332using SMLMSim
321333using 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))
409372end
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)
481411end
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)
517432fig
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