Figure 1#

A closed-loop virtual-reality active sensing task for mice.#

For reference, here is the full figure

Systems

and corresponding supplemental figure

Supplemental systems

cd "/app/"
%run env.py
%run run.py connect
from vr4mice.schema.base_analysis import DataFrame
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from vr4mice.analysis import plotting
from vr4mice.schema import base_analysis, dlc
from vr4mice.analysis.analysis import style

from vr4mice.schema.latency_tests import AllLatencies, SignalsPhotodiodeAligned

from vr4mice.analysis.latency_testing import find_rising_edges, get_latency
from matplotlib.colors import ListedColormap 

style()
save_fig_path = "notebooks/Paper_figures/Figure_output/"

Behavioral traces#

key = {"dataset": "Oribi_2024-08-14_1"}

df = DataFrame().get_data(
    key=key, columns=["dataset", "reward", "x", "y", "trial", "aperture", "iti"]
)
box_df = base_analysis.BoxDataFrame().get_data(key=key)

fig, ax = plt.subplots(1, 1, figsize=(5, 5))
plotting.plot_session(
    df=df[df.iti == 0.0], box_df=box_df, per_aperture=False, per_side=False, ax=ax
)
sns.despine(offset=10)
plt.savefig(
    save_fig_path + "figure1_example_session_trajectory_plot.svg", transparent=True
)
key = {"dataset": "Pheasant_2024-08-21_1"}

traces_to_plot = ["head_center_x", "head_center_y", "heading_dir", "head_angle"]
dlc_df = dlc.OfflineKinematics().get_data(key=key, columns=traces_to_plot)
df = DataFrame().get_data(
    key=key,
    columns=["dataset", "reward", "x", "y", "trial", "trial_step", "aperture", "iti"],
)
df = dlc_df.join(df)
df = df[df.iti == 0.0]
fig, ax = plt.subplots(5, 1, figsize=(10, 5), sharex=True, constrained_layout=True)
trial = df[df.trial.isin(range(10, 20))].copy().reset_index()
trial["trial_length"] = trial.groupby(["trial"], as_index=False)[
    "trial_step"
].transform(lambda x: x / x.max())

ax[0].plot(trial.x, c="black")
ax[0].set_ylim(-26, 26)
ax[0].set_xticks([])

ax[1].plot(trial.y, c="black")
ax[1].set_ylim(-30, 30)

ax[2].plot(trial.heading_dir, c="black")
ax[2].set_ylim(-180, 180)

ax[3].plot(trial.head_angle, c="black")

ax[4].plot(trial.trial_length, c="black")

plt.rc("axes.spines", top=False, bottom=False, left=False, right=False)
for a in ax:
    a.yaxis.tick_right()  # Moves the ticks to the right
    a.yaxis.set_label_position("right")  # Moves the label to the right
    a.spines["right"].set_position(("outward", 0))  # Adjust the right spine outward
    a.spines["left"].set_visible(False)  # Hide the left spine
    a.spines["right"].set_visible(True)

print(traces_to_plot, key)
plt.savefig(save_fig_path + "figure1_behavioural_var_traces.svg", transparent=True)

Latency tests#

# fetch latency data and transform into dataframe

mathis = (AllLatencies() & 'dataset LIKE "%Latencytest1%"').fetch( as_dict=True)
mathis_df = pd.concat([pd.DataFrame(d) for d in mathis])
mathis_df ["lab_id"] = "Mathis"
tolias = (AllLatencies() & 'dataset LIKE "%Latencytest2%"').fetch(as_dict=True)
tolias_df = pd.concat([pd.DataFrame(d) for d in tolias])
tolias_df ["lab_id"] = "Tolias"
niell = (AllLatencies() & 'dataset LIKE "%Latencytest3%"').fetch(as_dict=True)
niell_df = pd.concat([pd.DataFrame(d) for d in niell])
niell_df ["lab_id"] = "Niell"

latencies = pd.concat([mathis_df, tolias_df, niell_df])
mean_latencies =  latencies.groupby(["dataset", "lab_id"], as_index=False).mean()
mean_latencies [["dataset", "time_diff"]]
fig, ax = plt.subplots(1,3, figsize = (15,5), constrained_layout=True)
for i, lab in enumerate(latencies.lab_id.unique()):
    lab_latencies = latencies [latencies.lab_id == lab]
    g = sns.kdeplot(data=lab_latencies, x= lab_latencies.time_diff*1000, hue="dataset", palette=ListedColormap(['black']).colors, alpha=0.5, legend=False, ax=ax[i])
    ax[i].set_xlim(0,125)
    ax[i].set_xlabel("Latency (ms)")
    ax[i].axvline(np.mean(lab_latencies.time_diff*1000), c="red", linestyle="dashed", alpha = 0.7)
    print(f"lab = {lab} mean (ms): ", np.mean( lab_latencies.time_diff*1000), "std:", np.std( lab_latencies.time_diff*1000))

    sns.despine(offset=10)
    ax[i].set_title(lab)
plt.savefig(save_fig_path + "figure1_roundtrip_latency.svg")
fig, ax = plt.subplots(1,3, figsize = (15,5), constrained_layout=True)
for i, lab in enumerate(mean_latencies.lab_id.unique()):
    lab_latencies = mean_latencies [mean_latencies.lab_id == lab]

    sns.violinplot(x=np.repeat("DLClive", len(lab_latencies ["time_diff"])), y=abs(lab_latencies ["frame_to_socket"])*1000, color="cornflowerblue", ax=ax[i])
    sns.violinplot(x=np.repeat("Unity", len(lab_latencies ["time_diff"])), y=(abs(lab_latencies ["time_diff"])*1000) - (abs(lab_latencies ["frame_to_socket"])*1000), color="purple", ax=ax[i])
    sns.violinplot(x=np.repeat("Total", len(lab_latencies ["time_diff"])), y=abs(lab_latencies ["time_diff"])*1000, color="black", alpha=0.7,ax=ax[i])

    ax[i].set_ylabel("Latency (ms)")
    ax[i].set_ylim(0,120)
    sns.despine(offset=10)
    ax[i].set_title(lab)
plt.savefig(save_fig_path + "figure1_mean_latencies.svg")
# This fecth call takes some time as the photodide is sampled at 1000 hz
df = pd.DataFrame((SignalsPhotodiodeAligned() & 'dataset =  "Latencytest1_2024-10-31_2"').fetch(as_dict=True)[0])
rising_edges_signal = find_rising_edges(df.time_stamp, df.signal_read)
rising_edges_photodiode = find_rising_edges(df.time_stamp, df.photodiode_read)
latencies = get_latency(rising_edges_signal, rising_edges_photodiode)

start_time = df.time_stamp[np.where(df.signal_read == 1)[0][0]]
print("start_time", start_time)

fig, ax = plt.subplots(3,1,figsize=(10,5))
ax[0].plot(df.time_stamp, df.signal_read,c="black",linewidth=2)


ax[1].plot(df.time_stamp, df.photodiode_raw_scaled, c="#AE18D3",linewidth=2)

ax[2].plot(df.time_stamp,  df.signal_read, c="black",linewidth=2,alpha=0.8)
ax[2].plot(df.time_stamp,  df.photodiode_read, c="#AA1BCE",linewidth=2,alpha=0.8)

for ft, td in zip(latencies.frame_time, latencies.time_diff):
    ax[2].annotate(round(td*1000, 2), (ft, 1.1))

ax[1].set_ylim(-0.2,1.3)

ax[1].axhline(0.3,c="red",linestyle="dashed",alpha=0.5, linewidth=3)
ax[2].set_ylim(-0.2,1.3)


sns.despine(offset=10)

for a in ax:
    a.axis('off')
    a.set_xlim(start_time-1,start_time+3)

plt.savefig(save_fig_path + "figure1_signal.svg")
mean_latencies
fig, ax = plt.subplots(1,1, figsize = (5,5), constrained_layout=True)

sns.violinplot(data=mean_latencies, x="lab_id", y=mean_latencies.time_diff*1000, color="black", ax=ax, alpha=0.5)
   
ax.set_ylabel("Latency (ms)")
ax.set_xlabel("Lab")
ax.set_ylim(0,120)
sns.despine(offset=10)

plt.savefig(save_fig_path + "figure1_mean_latencies_lab.svg")
mean_latencies.groupby("lab_id")["time_diff"].mean() * 1000, mean_latencies.groupby("lab_id")["time_diff"].sem() * 1000