From 931274aaefa4575aa05ce64e5cf341826a0783bf Mon Sep 17 00:00:00 2001 From: rachelstephlee Date: Sat, 11 Jul 2026 01:06:14 +0000 Subject: [PATCH 1/4] improving FIP so that it's plotting the actual values. --- .../plot/plot_foraging_session_plotly.py | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py b/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py index 047834c..23fd9d5 100644 --- a/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py +++ b/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py @@ -940,30 +940,64 @@ def _trial_of(times, _gc=gc, _n=n_tr): if df_fip is not None and len(sessions) == 1 and len(fip) > 0: fip_channels = fip present = set(df_fip["event"].unique()) - band = 0 + band = 0.0 + fip_gap = 0.5 for channel in fip_channels: if channel not in present: continue - bottom = params["curve_top"] + 0.1 + band C = df_fip.query("event == @channel").copy() - d = C["data"].values - np.nanmin(C["data"].values) - d = d / np.nanmax(d) + bottom + vals = C["data"].astype(float).to_numpy() + if vals.size == 0 or np.all(np.isnan(vals)): + continue + vmin = np.nanmin(vals) + vmax = np.nanmax(vals) + span = vmax - vmin + # avoid zero-span stacking problems for constant signals + if np.isnan(span) or span == 0: + span = 1.0 + + if span < 0.04: + vmin = -0.05 + vmax = 0.05 + span = 0.1 + + # place this channel so its minimum maps to `base`, preserving original scale + base = params["curve_top"] + 0.1 + band + offset = base - vmin + d = vals + offset + color = get_fip_color(channel) + custom = np.stack([C.timestamps.values, vals], axis=-1) + hover = f"%{{customdata[0]:.2f}}s %{{customdata[1]:.3f}} {channel}" + fig.add_trace( go.Scattergl( x=C.timestamps.values + last_off, y=d, + customdata = custom, mode="lines", + hovertemplate=hover, line=dict(color=color), name=channel, ), row=1, col=1, ) - yticks.append(bottom + 0.5) - ylabels.append(channel) - band += 1 - y_main_top = bottom + 1.0 + + # use three ticks: bottom (vmin), center (channel name), top (vmax) + yticks.extend([base + 0.0, base + span / 2.0, base + span]) + ylabels.extend( + [ + f"{vmin:.2f}", + f"{channel.split('_dff')[0]}        ", + f"{vmax:.2f}", + ] + ) + + # advance band by the display span (not raw span) plus a small gap to avoid overlap + band += span + fip_gap + y_main_top = base + span + 0.25 + # Thick vertical lines marking session boundaries (both panels) for b in boundaries: @@ -1007,7 +1041,7 @@ def _trial_of(times, _gc=gc, _n=n_tr): # Title pinned to the very top-left so it clears the legend below it. title=dict(text=title or "Session Scroller", x=0.0, xanchor="left", y=0.98, yanchor="top"), showlegend=True, - height=620, + height=620 + (50 * len(fip) if df_fip is not None and len(fip) > 0 else 0), width=1000, template="simple_white", # Legend outside, top-left, horizontal, compact entries (narrow box). From b6a1f4ae9dbaad68788525e352781a898547acff Mon Sep 17 00:00:00 2001 From: rachelstephlee Date: Sat, 11 Jul 2026 07:11:16 +0000 Subject: [PATCH 2/4] fixed the ylabels so it's 0 and vmax --- .../plot/plot_foraging_session_plotly.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py b/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py index 23fd9d5..49273ae 100644 --- a/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py +++ b/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py @@ -956,10 +956,6 @@ def _trial_of(times, _gc=gc, _n=n_tr): if np.isnan(span) or span == 0: span = 1.0 - if span < 0.04: - vmin = -0.05 - vmax = 0.05 - span = 0.1 # place this channel so its minimum maps to `base`, preserving original scale base = params["curve_top"] + 0.1 + band @@ -985,11 +981,11 @@ def _trial_of(times, _gc=gc, _n=n_tr): ) # use three ticks: bottom (vmin), center (channel name), top (vmax) - yticks.extend([base + 0.0, base + span / 2.0, base + span]) + yticks.extend([base - vmin, base + span / 2.0, base + span]) ylabels.extend( [ - f"{vmin:.2f}", - f"{channel.split('_dff')[0]}        ", + f"0.0", + f"{channel.split('_dff')[0]}      ", f"{vmax:.2f}", ] ) From 1ca49d338bcf0a15f1f0291aed7519cbd087a794 Mon Sep 17 00:00:00 2001 From: rachelstephlee Date: Fri, 24 Jul 2026 22:48:38 +0000 Subject: [PATCH 3/4] added clipping --- .../plot/plot_foraging_session_plotly.py | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py b/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py index a635a9b..7a08d53 100644 --- a/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py +++ b/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py @@ -942,6 +942,8 @@ def _trial_of(times, _gc=gc, _n=n_tr): y_main_top = params["curve_top"] # FIP channels (single-session only), normalised and stacked above the behavior panel + fip_unclipped_ids = [] + fip_clipped_ids = [] if df_fip is not None and len(sessions) == 1 and len(fip) > 0: fip_channels = fip present = set(df_fip["event"].unique()) @@ -960,7 +962,6 @@ def _trial_of(times, _gc=gc, _n=n_tr): # avoid zero-span stacking problems for constant signals if np.isnan(span) or span == 0: span = 1.0 - # place this channel so its minimum maps to `base`, preserving original scale base = params["curve_top"] + 0.1 + band @@ -971,11 +972,12 @@ def _trial_of(times, _gc=gc, _n=n_tr): custom = np.stack([C.timestamps.values, vals], axis=-1) hover = f"%{{customdata[0]:.2f}}s %{{customdata[1]:.3f}} {channel}" + fip_unclipped_ids.append(len(fig.data)) fig.add_trace( go.Scattergl( x=C.timestamps.values + last_off, y=d, - customdata = custom, + customdata=custom, mode="lines", hovertemplate=hover, line=dict(color=color), @@ -985,6 +987,27 @@ def _trial_of(times, _gc=gc, _n=n_tr): col=1, ) + # clipped version: clip raw values to 1st–99th percentile, same y-offset + p01, p99 = np.nanpercentile(vals, 1), np.nanpercentile(vals, 99) + vals_clipped = np.clip(vals, p01, p99) + d_clipped = vals_clipped + offset + fip_clipped_ids.append(len(fig.data)) + fig.add_trace( + go.Scattergl( + x=C.timestamps.values + last_off, + y=d_clipped, + customdata=custom, + mode="lines", + hovertemplate=hover, + line=dict(color=color), + name=channel, + visible=False, + showlegend=False, + ), + row=1, + col=1, + ) + # use three ticks: bottom (vmin), center (channel name), top (vmax) yticks.extend([base - vmin, base + span / 2.0, base + span]) ylabels.extend( @@ -999,6 +1022,44 @@ def _trial_of(times, _gc=gc, _n=n_tr): band += span + fip_gap y_main_top = base + span + 0.25 + # Toggle button to switch FIP traces between full range and 1–99% clipped + if fip_unclipped_ids: + n_all = len(fig.data) + vis_full = [True] * n_all + vis_clip = [True] * n_all + for idx in fip_clipped_ids: + vis_full[idx] = False + for idx in fip_unclipped_ids: + vis_clip[idx] = False + for idx in fip_clipped_ids: + vis_clip[idx] = True + + fig.update_layout( + updatemenus=[ + dict( + type="buttons", + direction="right", + x=1.0, + y=1.0, + xanchor="right", + yanchor="top", + showactive=True, + buttons=[ + dict( + label="FIP: full range", + method="restyle", + args=[{"visible": vis_full}], + ), + dict( + label="FIP: clip 1–99%", + method="restyle", + args=[{"visible": vis_clip}], + ), + ], + ) + ] + ) + # Thick vertical lines marking session boundaries (both panels) for b in boundaries: From c5126b47b715a967edbabfce9b76315b945965cd Mon Sep 17 00:00:00 2001 From: rachelstephlee Date: Tue, 28 Jul 2026 23:18:56 +0000 Subject: [PATCH 4/4] linting --- .../plot/plot_foraging_session_plotly.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py b/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py index 7a08d53..23b689d 100644 --- a/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py +++ b/src/aind_dynamic_foraging_basic_analysis/plot/plot_foraging_session_plotly.py @@ -1012,7 +1012,7 @@ def _trial_of(times, _gc=gc, _n=n_tr): yticks.extend([base - vmin, base + span / 2.0, base + span]) ylabels.extend( [ - f"0.0", + "0.0", f"{channel.split('_dff')[0]}      ", f"{vmax:.2f}", ] @@ -1059,8 +1059,6 @@ def _trial_of(times, _gc=gc, _n=n_tr): ) ] ) - - # Thick vertical lines marking session boundaries (both panels) for b in boundaries: for row in (1, 2):