1+ # using GLMakie
2+ # using Statistics
3+ # using ElectroPhysiology, PhysiologyAnalysis
4+
5+ """
6+ plot_roi_analysis(data::Experiment{TWO_PHOTON};
7+ stim_idx::Int=1, channel_idx::Union{Int,Nothing}=nothing)
8+
9+ Create a comprehensive visualization of ROI analysis results for a single stimulus event including:
10+ 1. ROI map showing significant ROIs weighted by their amplitude, overlaid on max projection
11+ 2. Individual dF/F traces for significant ROIs
12+ 3. Mean trace with standard deviation ribbon
13+
14+ If channel_idx is provided, only that channel will be shown. Otherwise, all channels will be displayed.
15+
16+ Returns a Figure object containing all plots.
17+ """
18+ function plot_roi_analysis (data:: Experiment{TWO_PHOTON, T} ;
19+ stim_idx:: Int = 1 , channel_idx:: Union{Int,Nothing} = nothing ) where {T <: Real }
20+
21+ @assert haskey (data. HeaderDict, " ROI_Analysis" ) " Data must contain ROI analysis results in HeaderDict"
22+
23+ analysis = data. HeaderDict[" ROI_Analysis" ]
24+
25+ # Plot max projection as background
26+ xlims = data. HeaderDict[" xrng" ]
27+ ylims = data. HeaderDict[" yrng" ]
28+
29+ # Determine which channels to process
30+ channels_to_process = isnothing (channel_idx) ? analysis. channels : [channel_idx]
31+ n_channels = length (channels_to_process)
32+
33+ # Create figure with layout
34+ fig = Figure (size= (1200 * n_channels, 800 ))
35+
36+ # Process each channel
37+ for (ch_idx, channel) in enumerate (channels_to_process)
38+ # Get significant ROIs for this channel and stimulus
39+ sig_rois = get_significant_rois (analysis, stim_idx, channel)
40+
41+ # Create a 2x2 grid for this channel
42+ gl_channel = fig[1 , ch_idx] = GridLayout ()
43+
44+ # 1. ROI amplitude map with max projection (top left)
45+ ax1 = Axis (gl_channel[1 ,1 ], title= " Channel $channel Response Map (Stimulus $stim_idx )" ,
46+ xlabel= " X Position (μm)" , ylabel= " Y Position (μm)" ,
47+ aspect= DataAspect ())
48+
49+ # Get max projection of original data
50+ max_proj = project (data, dims = (3 ))[:,:,1 ,channel]
51+ max_proj = rotr90 (max_proj)
52+ norm_max_proj = (max_proj .- minimum (max_proj)) ./ (maximum (max_proj) - minimum (max_proj))
53+ hm1 = heatmap! (ax1, xlims, ylims, norm_max_proj)
54+ Colorbar (gl_channel[1 ,2 ], hm1, label= " dF/F" , width= 15 , vertical= true )
55+
56+ # Create a sub-grid for the traces
57+ gl_traces = gl_channel[1 ,3 ] = GridLayout ()
58+
59+ # 2. Mean trace with std ribbon (top right, left half)
60+ ax2 = Axis (gl_traces[1 ,1 ], title= " Mean Response ± STD (Stimulus $stim_idx )" ,
61+ xlabel= " Time (s)" , ylabel= " ΔF/F" )
62+
63+ if ! isempty (sig_rois)
64+ # Get all significant traces and calculate statistics
65+ sig_traces = [filter (t -> t. channel == channel && t. stimulus_index == stim_idx, analysis. rois[roi])[1 ]. dfof for roi in sig_rois]
66+ t_series = filter (t -> t. channel == channel && t. stimulus_index == stim_idx, analysis. rois[first (sig_rois)])[1 ]. t_series
67+
68+ mean_trace = mean (sig_traces)
69+ std_trace = std (sig_traces)
70+
71+ # Plot mean and standard deviation
72+ band! (ax2, t_series,
73+ mean_trace .- std_trace,
74+ mean_trace .+ std_trace,
75+ color= (:blue , 0.3 ))
76+ lines! (ax2, t_series, mean_trace,
77+ color= :blue , linewidth= 2 ,
78+ label= " Mean (n=$(length (sig_rois)) )" )
79+ end
80+
81+ # 3. Individual traces (top right, right half)
82+ ax3 = Axis (gl_traces[2 ,1 ], title= " Individual ROI Traces (Stimulus $stim_idx )" ,
83+ xlabel= " Time (s)" , ylabel= " ΔF/F" )
84+
85+ if ! isempty (sig_rois)
86+ # Plot each significant ROI trace
87+ colors = cgrad (:viridis , length (sig_rois), categorical= true )
88+ for (i, roi_id) in enumerate (sig_rois)
89+ traces = filter (t -> t. channel == channel && t. stimulus_index == stim_idx, analysis. rois[roi_id])
90+ trace = traces[1 ]
91+ lines! (ax3, trace. t_series, trace. dfof,
92+ color= colors[i], alpha= 0.5 )
93+ end
94+ end
95+
96+ # Link y-axes of the traces
97+ linkyaxes! (ax2, ax3)
98+
99+ # Get ROI mask and create weighted map
100+ roi_mask = getROImask (data)
101+ roi_mask = rotr90 (roi_mask)
102+ weighted_mask = zeros (size (roi_mask))
103+ tau_off_mask = zeros (size (roi_mask))
104+
105+ for roi_id in keys (analysis. rois)
106+ if roi_id in sig_rois
107+ traces = filter (t -> t. channel == channel && t. stimulus_index == stim_idx, analysis. rois[roi_id])
108+ trace = traces[1 ]
109+ max_response = maximum (trace. dfof)
110+ weighted_mask[roi_mask .== roi_id] .= max_response
111+ if ! isnothing (trace. fit_parameters) && length (trace. fit_parameters) >= 3
112+ tau_off_mask[roi_mask .== roi_id] .= trace. fit_parameters[3 ]
113+ end
114+ end
115+ end
116+
117+ # 4. Weighted response map (bottom left)
118+ ax_weighted = Axis (gl_channel[2 ,1 ], title= " Channel $channel Weighted Response (Stimulus $stim_idx )" ,
119+ xlabel= " X Position (μm)" , ylabel= " Y Position (μm)" ,
120+ aspect= DataAspect ())
121+
122+ if ! isempty (sig_rois)
123+ hm_weighted = heatmap! (ax_weighted, xlims, ylims, weighted_mask,
124+ colormap= :viridis ,
125+ colorrange= (0 , maximum (weighted_mask)))
126+ Colorbar (gl_channel[2 ,2 ], hm_weighted, label= " dF/F" , width= 15 , vertical= true )
127+ else
128+ text! (ax_weighted, " No significant ROIs found" ,
129+ position= (mean (xlims), mean (ylims)),
130+ align= (:center , :center ),
131+ color= :red )
132+ end
133+
134+ # 5. Tau Off map (bottom right)
135+ ax_tau = Axis (gl_channel[2 ,3 ], title= " Channel $channel Tau Off (Stimulus $stim_idx )" ,
136+ xlabel= " X Position (μm)" , ylabel= " Y Position (μm)" ,
137+ aspect= DataAspect ())
138+
139+ if ! isempty (sig_rois)
140+ hm_tau = heatmap! (ax_tau, xlims, ylims, tau_off_mask,
141+ colormap= :viridis ,
142+ colorrange= (0 , maximum (filter (! iszero, tau_off_mask))))
143+ Colorbar (gl_channel[2 ,4 ], hm_tau, label= " Tau Off (s)" , width= 15 , vertical= true )
144+ else
145+ text! (ax_tau, " No significant ROIs found" ,
146+ position= (mean (xlims), mean (ylims)),
147+ align= (:center , :center ),
148+ color= :red )
149+ end
150+
151+ # Add stimulus time indicator if available
152+ if haskey (analysis. analysis_parameters, :delay_time )
153+ delay_time = analysis. analysis_parameters[:delay_time ]
154+ vlines! (ax2, [delay_time], color= :red , linestyle= :dash , label= " Stimulus" )
155+ vlines! (ax3, [delay_time], color= :red , linestyle= :dash , label= " Stimulus" )
156+ end
157+
158+ # Add legend only for mean response
159+ axislegend (ax2, position= :rt )
160+ end
161+
162+ return fig
163+ end
164+
165+ """
166+ plot_roi_analysis_averaged(data::Experiment{TWO_PHOTON};
167+ channel_idx::Union{Int,Nothing}=nothing)
168+
169+ Create a visualization showing the average responses across all stimuli for each ROI, including:
170+ 1. ROI map showing significant ROIs weighted by their average amplitude across stimuli
171+ 2. Average dF/F traces across all stimuli for each significant ROI
172+ 3. Overall mean trace with standard deviation ribbon
173+
174+ If channel_idx is provided, only that channel will be shown. Otherwise, all channels will be displayed.
175+
176+ Returns a Figure object containing all plots.
177+ """
178+ function plot_roi_analysis_averaged (data:: Experiment{TWO_PHOTON, T} ;
179+ channel_idx:: Union{Int,Nothing} = nothing ) where {T <: Real }
180+
181+ @assert haskey (data. HeaderDict, " ROI_Analysis" ) " Data must contain ROI analysis results in HeaderDict"
182+
183+ analysis = data. HeaderDict[" ROI_Analysis" ]
184+ # Plot max projection as background
185+ xlims = data. HeaderDict[" xrng" ]
186+ ylims = data. HeaderDict[" yrng" ]
187+
188+ # Determine which channels to process
189+ channels_to_process = isnothing (channel_idx) ? analysis. channels : [channel_idx]
190+ n_channels = length (channels_to_process)
191+
192+ # Create figure with layout
193+ fig = Figure (size= (1200 * n_channels, 800 ))
194+
195+ # Process each channel
196+ for (ch_idx, channel) in enumerate (channels_to_process)
197+ # Get significant ROIs for this channel (significant in any stimulus)
198+ sig_rois = unique ([id for (id, traces) in analysis. rois
199+ if any (t -> t. channel == channel && t. is_significant, traces)])
200+
201+ # Create a 2x2 grid for this channel
202+ gl_channel = fig[1 , ch_idx] = GridLayout ()
203+
204+ # 1. ROI amplitude map with max projection (top left)
205+ ax1 = Axis (gl_channel[1 ,1 ], title= " Channel $channel Response Map (Averaged)" ,
206+ xlabel= " X Position (μm)" , ylabel= " Y Position (μm)" ,
207+ aspect= DataAspect ())
208+
209+ # Get max projection of original data
210+ max_proj = project (data, dims = (3 ))[:,:,1 ,channel]
211+ max_proj = rotr90 (max_proj)
212+ norm_max_proj = (max_proj .- minimum (max_proj)) ./ (maximum (max_proj) - minimum (max_proj))
213+ hm1 = heatmap! (ax1, xlims, ylims, norm_max_proj)
214+ Colorbar (gl_channel[1 ,2 ], hm1, label= " dF/F" , width= 15 , vertical= true )
215+
216+ # Create a sub-grid for the traces
217+ gl_traces = gl_channel[1 ,3 ] = GridLayout ()
218+
219+ # 2. Mean trace with std ribbon (top right, left half)
220+ ax2 = Axis (gl_traces[1 ,1 ], title= " Mean Response ± STD (Averaged)" ,
221+ xlabel= " Time (s)" , ylabel= " ΔF/F" )
222+
223+ if ! isempty (sig_rois)
224+ # Calculate average traces across all stimuli for each ROI
225+ avg_traces = []
226+ t_series = nothing
227+
228+ for roi_id in sig_rois
229+ traces = filter (t -> t. channel == channel, analysis. rois[roi_id])
230+ if ! isempty (traces)
231+ # Average the traces for this ROI across all stimuli
232+ roi_avg = mean (t. dfof for t in traces)
233+ push! (avg_traces, roi_avg)
234+ if isnothing (t_series)
235+ t_series = traces[1 ]. t_series
236+ end
237+ end
238+ end
239+
240+ if ! isempty (avg_traces)
241+ mean_trace = mean (avg_traces)
242+ std_trace = std (avg_traces)
243+
244+ # Plot mean and standard deviation
245+ band! (ax2, t_series,
246+ mean_trace .- std_trace,
247+ mean_trace .+ std_trace,
248+ color= (:blue , 0.3 ))
249+ lines! (ax2, t_series, mean_trace,
250+ color= :blue , linewidth= 2 ,
251+ label= " Mean (n=$(length (sig_rois)) )" )
252+ end
253+ end
254+
255+ # 3. Individual averaged traces (top right, right half)
256+ ax3 = Axis (gl_traces[2 ,1 ], title= " Individual ROI Traces (Averaged)" ,
257+ xlabel= " Time (s)" , ylabel= " ΔF/F" )
258+
259+ if ! isempty (sig_rois)
260+ # Plot each ROI's average trace
261+ colors = cgrad (:viridis , length (sig_rois), categorical= true )
262+ for (i, roi_id) in enumerate (sig_rois)
263+ traces = filter (t -> t. channel == channel, analysis. rois[roi_id])
264+ if ! isempty (traces)
265+ roi_avg = mean (t. dfof for t in traces)
266+ lines! (ax3, traces[1 ]. t_series, roi_avg,
267+ color= colors[i], alpha= 0.5 )
268+ end
269+ end
270+ end
271+
272+ # Link y-axes of the traces
273+ linkyaxes! (ax2, ax3)
274+
275+ # Get ROI mask and create weighted map
276+ roi_mask = getROImask (data)
277+ roi_mask = rotr90 (roi_mask)
278+ weighted_mask = zeros (size (roi_mask))
279+ tau_off_mask = zeros (size (roi_mask))
280+
281+ for roi_id in keys (analysis. rois)
282+ if roi_id in sig_rois
283+ traces = filter (t -> t. channel == channel, analysis. rois[roi_id])
284+ if ! isempty (traces)
285+ # Average the maximum response across all stimuli
286+ max_responses = [maximum (t. dfof) for t in traces]
287+ avg_max_response = mean (max_responses)
288+ weighted_mask[roi_mask .== roi_id] .= avg_max_response
289+
290+ # Average tau_off across all stimuli
291+ tau_offs = [t. fit_parameters[3 ] for t in traces
292+ if ! isnothing (t. fit_parameters) && length (t. fit_parameters) >= 3 ]
293+ if ! isempty (tau_offs)
294+ tau_off_mask[roi_mask .== roi_id] .= mean (tau_offs)
295+ end
296+ end
297+ end
298+ end
299+
300+ # 4. Weighted response map (bottom left)
301+ ax_weighted = Axis (gl_channel[2 ,1 ], title= " Channel $channel Weighted Response (Averaged)" ,
302+ xlabel= " X Position (μm)" , ylabel= " Y Position (μm)" ,
303+ aspect= DataAspect ())
304+
305+ if ! isempty (sig_rois)
306+ hm_weighted = heatmap! (ax_weighted, xlims, ylims, weighted_mask,
307+ colormap= :viridis ,
308+ colorrange= (0 , maximum (weighted_mask)))
309+ Colorbar (gl_channel[2 ,2 ], hm_weighted, label= " dF/F" , width= 15 , vertical= true )
310+ else
311+ text! (ax_weighted, " No significant ROIs found" ,
312+ position= (mean (xlims), mean (ylims)),
313+ align= (:center , :center ),
314+ color= :red )
315+ end
316+
317+ # 5. Tau Off map (bottom right)
318+ ax_tau = Axis (gl_channel[2 ,3 ], title= " Channel $channel Tau Off (Averaged)" ,
319+ xlabel= " X Position (μm)" , ylabel= " Y Position (μm)" ,
320+ aspect= DataAspect ())
321+
322+ if ! isempty (sig_rois)
323+ hm_tau = heatmap! (ax_tau, xlims, ylims, tau_off_mask,
324+ colormap= :viridis ,
325+ colorrange= (0 , maximum (filter (! iszero, tau_off_mask))))
326+ Colorbar (gl_channel[2 ,4 ], hm_tau, label= " Tau Off (s)" , width= 15 , vertical= true )
327+ else
328+ text! (ax_tau, " No significant ROIs found" ,
329+ position= (mean (xlims), mean (ylims)),
330+ align= (:center , :center ),
331+ color= :red )
332+ end
333+
334+ # Add stimulus time indicator if available
335+ if haskey (analysis. analysis_parameters, :delay_time )
336+ delay_time = analysis. analysis_parameters[:delay_time ]
337+ vlines! (ax2, [delay_time], color= :red , linestyle= :dash , label= " Stimulus" )
338+ vlines! (ax3, [delay_time], color= :red , linestyle= :dash , label= " Stimulus" )
339+ end
340+
341+ # Add legend only for mean response
342+ axislegend (ax2, position= :rt )
343+ end
344+
345+ return fig
346+ end
0 commit comments