{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# include controllers to the path\n", "import sys, os\n", "sys.path.append(os.getcwd())\n", "sys.path.append(os.path.join(os.getcwd(), 'controllers'))\n", "\n", "# include pipeline repo to compute performance\n", "sys.path.append(os.path.join(os.getcwd(), '..'))\n", "sys.path.append(os.path.join(os.getcwd(), '..', 'pipeline'))\n", "sys.path.append(os.path.join(os.getcwd(), '..', 'pipeline', 'postprocessing'))\n", "\n", "import cv2\n", "import threading\n", "import math\n", "import time\n", "import random\n", "import json\n", "import datetime\n", "import os, shutil\n", "import numpy as np\n", "import multiprocess as mp\n", "\n", "# controllers\n", "import nbimporter\n", "from controllers.situtils import FPSTimes\n", "from controllers.camera import WebcamStream\n", "from controllers.video import VideoWriter\n", "from controllers.microphones import MicrophoneController\n", "from controllers.position import PositionTrackerSingle, PositionTrackerDouble\n", "from controllers.sound import SoundController, ContinuousSoundStream\n", "from controllers.serial import MCSArduino, FakeArduino, SpeakerMotor, CableMotor\n", "from controllers.display import SITDisplay\n", "from controllers.island import IslandFactory\n", "\n", "from pipeline.postprocessing.pack import pack\n", "from pipeline.postprocessing.performance import calculate_performance, dump_performance_to_H5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load experiment settings\n", "\n", "For every experimental cofiguration you can copy the original 'settings.json' file, build your own specific experimental preset, save it in this folder as e.g. 'settings_elena.json' and load it here instead of 'settings.json'." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "#cfg_filename = os.path.join('profiles', 'gokce_timeSIT_50_52.5.json')\n", "#cfg_filename = os.path.join('profiles', 'gokce_timeSIT_90_60.json')\n", "#'gokce_timeSIT_90_108.json')\n", "\n", "# cfg_filename = os.path.join('profiles', 'social_test_actual.json')\n", "cfg_filename = os.path.join('profiles', 'andrey_ppcSIT_SL_009266.json')" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "scrolled": false }, "outputs": [], "source": [ "with open(os.path.join('profiles', 'default.json')) as json_file:\n", " cfg = json.load(json_file)\n", "with open(cfg_filename) as json_file:\n", " cfg_local = json.load(json_file)\n", "\n", "for key in cfg.keys():\n", " if key in cfg_local: # only update if the key exists in the local config, otherwise keep default (important for backward compatibility with cfg files before microphones)\n", " cfg[key].update(cfg_local[key])\n", "cfg['experiment']['experiment_date'] = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')\n", "\n", "# print loaded settings\n", "#print(json.dumps(cfg, indent=4))" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# check if the sound interface is there\n", "import sounddevice as sd\n", "asio = [x for x in sd.query_devices() if x['name'].find('ASIO') > 0]\n", "#if len(asio) == 0:\n", "# raise SystemExit('The sound interface is not found. Please restart the computer')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initialize session folder\n", "\n", "Run the upcoming cell, to create a session folder and to save the chosen experimetal parameters to a JSON-file (\"experiment_id_parameters.json\"). The session folder will be created here where this notebook is located." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# This session's protocols will be saved to this folder\n", "cfg_exp = cfg['experiment']\n", "experiment_id = \"%s_%s_%s\" % (cfg_exp['subject'], cfg_exp['experiment_type'], cfg_exp['experiment_date'])\n", "save_to = os.path.join('sessions', experiment_id)\n", " \n", "if not os.path.exists(save_to):\n", " os.makedirs(save_to)\n", "\n", "# update paths (assuming this paths are relative to this notebook)\n", "cfg['video']['file_path'] = os.path.join(save_to, cfg['video']['file_path'])\n", "cfg['video']['csv_path'] = os.path.join(save_to, cfg['video']['csv_path'])\n", "cfg['microphones']['file_path'] = os.path.join(save_to, cfg['microphones']['file_path'])\n", "cfg['position']['file_path'] = os.path.join(save_to, cfg['position']['file_path'])\n", "cfg['position']['contour_path'] = os.path.join(save_to, cfg['position']['contour_path'])\n", "cfg['experiment']['file_path'] = os.path.join(save_to, cfg['experiment']['file_path'])\n", "cfg['experiment']['islands_path'] = os.path.join(save_to, 'islands.csv')\n", "cfg['sound']['file_path'] = os.path.join(save_to, cfg['sound']['file_path'])\n", "cfg['position']['background_light'] = os.path.join('assets', cfg['position']['background_light'])\n", "cfg['position']['background_dark'] = os.path.join('assets', cfg['position']['background_dark'])\n", "if 'continuous' in cfg['sound']:\n", " cfg['sound']['continuous']['wav_file'] = os.path.join('assets', cfg['sound']['continuous']['wav_file'])\n", " \n", "# Saves all parameters to a JSON file with the user-defined \"Experiment ID\" as filename\n", "with open(os.path.join(save_to, experiment_id + '.json'), 'w') as f:\n", " json.dump(cfg, f, indent=4)\n", " \n", "with open(cfg['experiment']['file_path'], 'w') as f:\n", " # state: 0 - trial start, 1 - trial success, 2 - trial fail\n", " f.write('time, target_x, target_y, target_r, trial, state\\n')\n", "\n", "with open(cfg['experiment']['islands_path'], 'w') as f:\n", " f.write('tgt_x, tgt_y, tgt_r, d1_x, d1_y, d1_r, d2_x, d2_y, d2_r, d3_x, d3_y, d3_r\\n')" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def timeout(t_start):\n", " return time.time() - t_start > cfg_exp['session_duration'] if t_start is not None else False" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def log_event(*args): # log start / end of a trial\n", " with open(cfg_exp['file_path'], 'a') as f:\n", " f.write(\",\".join([str(x) for x in args]) + \"\\n\")" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def log_islands(islands): # log position of the islands\n", " sorted_islands = sorted(islands, key=lambda x: x.sound_id, reverse=False)\n", " args = [(i.x, i.y, i.r) for i in sorted_islands]\n", " to_dump = [x for xs in args for x in xs]\n", " \n", " with open(cfg_exp['islands_path'], 'a') as f: \n", " f.write(\",\".join([str(round(x, 4)) for x in to_dump]) + \"\\n\")" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "def switch_light(pt, board):\n", " pt.switch_background()\n", " board.switch_light()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Start the experiment\n", "\n", "This cell contains code for animal tracking. We hope that the comments provided in the code suffice to understand the individual steps and to adjust them to your own setup and needs, if necessary.\n", "\n", "- press 's' to start recording\n", "- press 's' again to stop recording\n", "- press 'q' to quit\n", "\n", "The experiment will stop automatically if the pre-defined session duration is reached." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Webcam stream 1024.0:768.0 at 30.00 FPS started\n", "Position tracker stopped\n", "Video writer stopped\n", "Camera released\n" ] } ], "source": [ "# actual sound selector: -1 - noise, 0 - silence, 1 - foraging, 2 - target, 3 - distractor\n", "sound = mp.Value('i', 1)\n", "if 'noise_when_idle' in cfg_exp and cfg_exp['noise_when_idle']:\n", " sound.value = -1\n", "\n", "# experiment status: 1 - idle, 2 - running (recording, logging), 0 - stopped\n", "status = mp.Value('i', 1)\n", "\n", "# init the sync with the acquisition system and feeder via Arduino\n", "if cfg['experiment']['MCSArduinoPort'] == 'fake':\n", " board = FakeArduino()\n", "else:\n", " board = MCSArduino(cfg['experiment']['MCSArduinoPort'])\n", "\n", "# init speaker motor control\n", "if 'motors_port' in cfg['experiment'] and cfg['experiment']['enable_motors']:\n", " motor_board = SpeakerMotor(cfg['experiment']['motors_port'])\n", " \n", "# init ephys cable rotation motor\n", "if 'cable_motor_port' in cfg['experiment']:\n", " cable_board = CableMotor(cfg['experiment']['cable_motor_port'])\n", "\n", "# init continuous sound, if required\n", "if 'continuous' in cfg['sound']:\n", " cst = ContinuousSoundStream(cfg['sound']['continuous'])\n", " cst.start()\n", " \n", "# start the camera stream\n", "vs = WebcamStream(cfg['camera'])\n", "vs.start()\n", "\n", "# init video recorder\n", "vw = VideoWriter(status, vs, cfg['video'])\n", "vw.start()\n", "\n", "# init microphone controller, if required\n", "if cfg['microphones']['record_audio']:\n", " mc = mp.Process(target=MicrophoneController.run, args=(status,cfg[\"microphones\"]))\n", " mc.start()\n", "\n", "# start position tracking\n", "pt = PositionTrackerSingle(status, vs, cfg['position']) if cfg['position']['single_agent'] else PositionTrackerDouble(status, vs, cfg['position'])\n", "pt.start()\n", "# print(pt.mask.shape)\n", "# init frame renderer\n", "dc = SITDisplay(pt, cfg['video'])\n", "\n", "# playing sound in a separate process for performance\n", "sc = mp.Process(target=SoundController.run, args=(sound, status, cfg['sound']))\n", "sc.start()\n", "\n", "cfg_pos = cfg['position']\n", "isl_factory = IslandFactory(cfg_pos['floor_r_in_meters'], cfg_pos['angle_compensation'], cfg['experiment'])\n", "\n", "timers = []\n", "fps = FPSTimes()\n", "names = ['camera', 'video', 'position', 'main']\n", "distr_count = cfg['experiment']['distractor_islands']\n", "trial = 0\n", "rewards = 0\n", "t_start = None\n", "target_since = None\n", "distractor_since = None\n", "punishment_since = None\n", "trial_start = time.time()\n", "phase = 0 # 0 - idle, 1 - foraging, 2 - inter-trial interval\n", "cfg_exp = cfg['experiment']\n", "cfg_pos = cfg['position']\n", "islands = []\n", "iti_distance = 0.0\n", "last_x, last_y = None, None\n", "\n", "# initial position of the island, if set\n", "#phi_initial = np.random.rand() * 2 * np.pi if cfg_exp['target_angle'] == 'random' else np.deg2rad(int(cfg_exp['target_angle']))\n", "#rho = pt.cfg['floor_radius'] * pt.pixel_size - cfg_exp['target_radius'] # constant, in meters\n", "\n", "\n", "# inaudible island trial profiles\n", "max_trials = 200\n", "p0 = np.array([0 for x in range(max_trials)]) # never\n", "p1 = np.array([0 if (x+1) % 4 > 0 else 1 for x in range(max_trials)]) # every 4-th trial\n", "p2 = np.array([0 if (x+1) % 2 > 0 else 1 for x in range(max_trials)]) # every 2-nd trial\n", "p3 = np.array([1 if x % 4 > 0 else 0 for x in range(max_trials)]) # always except every 4-th trial\n", "unaudible_trials = np.vstack([p0, p1, p2, p3])\n", "\n", "## TODO refactor a) trial init, b) trial failure and c) trial success as separate functions - read about state mgmt\n", "\n", "try:\n", " while trial <= cfg_exp['trial_number'] and not timeout(t_start):\n", " \n", " # ---------- rendering logic -----------\n", " frame = vs.read()\n", " if frame is None:\n", " time.sleep(0.1)\n", " continue # wait for the stream\n", " \n", " c_time = time.time()\n", " fps.count()\n", " angles, text_infos = [], []\n", " \n", " if not cfg_exp['target_angle'] == 'random':\n", " phi = isl_factory.phi_initial\n", " angles = [pt.correct_angle(phi), pt.correct_angle(phi) + np.deg2rad(cfg_exp['phi_max'])]\n", " \n", " for i, ctrl in enumerate([vs, vw, pt, fps]): # FPS indicators\n", " text_infos.append('%s: %.2f FPS' % (names[i], ctrl.get_avg_fps()))\n", " \n", " if len(islands) > 0: # target island X, Y\n", " target = [i for i in islands if not i.is_distractor][0]\n", " text_infos.append('Target: %.3f %.3f' % (target.x, target.y))\n", "# print(frame.shape) \n", " text_infos.append('Time: %.2f' % float(c_time - t_start) if t_start is not None else 'Time: Idle') # stopwatch\n", " text_infos.append('Trial: %.2f' % float(cfg_exp['trial_duration'] - (c_time - trial_start)) if phase == 1 else 'Trial: not started')\n", " text_infos.append('Trial: %s' % trial)\n", " text_infos.append('Rewards: %s' % rewards) # rewards\n", " text_infos.append('In target: %.2f' % float(c_time - target_since) if target_since is not None else 'In target: --')\n", " text_infos.append('ITI distance: %.2f' % iti_distance if iti_distance > 0 else 'ITI distance: --')\n", "\n", " frame, frame_to_save = dc.render(frame, status, islands=islands, angles=angles, text_infos=text_infos)\n", " \n", " # assign the frame back to the video stream for other controllers\n", " vs.frame_with_infos = frame_to_save\n", " \n", " cv2.imshow('Press (s)-to start/stop, (q)-to end', frame)\n", "\n", " # -------- experiment logic ---------------\n", " \n", " # animals is either foraging (phase == 1) or in the inter-trial-interval (phase == 2)\n", " if phase == 1: # foraging\n", " if pt.positions_in_m is not None and len(islands) > 0:\n", " # check if animal in the island and for how long\n", " tgt = [i for i in islands if not i.is_distractor][0]\n", " distractors = [i for i in islands if i.is_distractor]\n", " \n", " if pt.is_inside(tgt.x, tgt.y, cfg_exp['target_radius']):\n", " if target_since is None: # just entered target\n", " target_since = c_time\n", " if unaudible_trials[cfg_exp['unaudible_profile']][trial - 1] == 0:\n", " sound.value = 2\n", "\n", " elif c_time - target_since > cfg_exp['target_duration']: # successful trial\n", " log_event(c_time, round(tgt.x, 4), round(tgt.y, 4), round(tgt.r, 4), trial, 1) # log trial success\n", " board.feed()\n", " \n", " # init inter-trial interval and new trial\n", " trial_start = c_time + cfg_exp['iti_duration'] + 5 * np.random.rand() # random b/w 20-25 sec\n", " sound.value = 0 # silence\n", " islands = []\n", " phase = 2\n", " rewards += 1\n", " target_since = None\n", " \n", " elif c_time - trial_start > cfg_exp['trial_duration']: # trial failed and animal is not in the target\n", " ## TODO refactor this + trial fail below into a function\n", " log_event(c_time, round(tgt.x, 4), round(tgt.y, 4), round(tgt.r, 4), trial, 2) # log trial failed because of time\n", " trial_start = c_time + cfg_exp['iti_duration'] + 5 * np.random.rand() # random b/w 10-15 sec\n", " sound.value = -1 # punishment\n", " punishment_since = c_time\n", " islands = []\n", " phase = 2\n", " distractor_since = None\n", " target_since = None # take care of the case in which animal was in target at trial end but exited before target_duration\n", " \n", " else:\n", " target_since = None\n", " in_distractor = False\n", " \n", " for isl in distractors: # maybe animal is in one of the distractors\n", " if pt.is_inside(isl.x, isl.y, cfg_exp['target_radius']):\n", " if distractor_since is None: # just entered distractor\n", " distractor_since = c_time\n", " \n", " sound.value = isl.sound_id\n", " in_distractor = True\n", " \n", " if c_time - distractor_since > cfg_exp['target_duration'] and cfg_exp['distractor_fail']:\n", " # fail trial because in distractor\n", " log_event(c_time, round(tgt.x, 4), round(tgt.y, 4), round(tgt.r, 4), trial, 2) # log trial failed\n", " trial_start = c_time + cfg_exp['iti_duration'] + 5 * np.random.rand() # random b/w 10-15 sec\n", " sound.value = -1 # punishment\n", " punishment_since = c_time\n", " islands = []\n", " phase = 2\n", " distractor_since = None\n", " else:\n", " distractor_since = None\n", " \n", " if not in_distractor: # outside of the islands\n", " sound.value = 1\n", " \n", " elif phase == 2: # inter-trial-interval\n", " # count distance between trials\n", " if last_x is None:\n", " last_x, last_y = pt.positions_in_m[0]\n", " else:\n", " x, y = pt.positions_in_m[0]\n", " iti_distance += np.sqrt((x - last_x)**2 + (y - last_y)**2)\n", " last_x, last_y = x, y\n", " \n", " # reset punishment sound\n", " if punishment_since is not None and c_time - punishment_since > cfg_exp['punishment_duration']:\n", " sound.value = 0\n", " punishment_since = None\n", "\n", " if c_time > trial_start and iti_distance > cfg_exp['iti_distance'] and \\\n", " isl_factory.last_tgt_x is not None and not pt.is_inside(isl_factory.last_tgt_x, isl_factory.last_tgt_y, cfg_exp['target_radius']):\n", " ## TODO refactor this + trial init below into a function\n", " # init_new_trial\n", " islands = isl_factory.generate_islands(c_time - t_start)\n", " log_islands(islands) # log island(s) positions\n", " sound.value = 1\n", " phase = 1\n", " trial += 1\n", " \n", " # reset ITI distance and trial_start\n", " iti_distance = 0\n", " trial_start = c_time\n", " \n", " # log trial start\n", " tgt = [i for i in islands if not i.is_distractor][0]\n", " log_event(c_time, round(tgt.x, 4), round(tgt.y, 4), round(tgt.r, 4), trial, 0)\n", " \n", " # -------- key press events ---------------\n", " \n", " k = cv2.waitKey(33)\n", " \n", " # light on/off\n", " if k == ord('l'):\n", " switch_light(pt, board)\n", " \n", " # motors\n", " if k == ord('d'):\n", " if cfg['experiment']['enable_motors']:\n", " motor_board.switch_diodes()\n", " \n", " # disable motor control for safety\n", " #if k == ord('t'):\n", " # tm = threading.Timer(20, motor_board.rotate, args=(False, 90, 30))\n", " # tm.start()\n", " # #motor_board.rotate(False, 90, 30)\n", " #if k == ord('r'):\n", " # tm = threading.Timer(20, motor_board.rotate, args=(True, 90, 30))\n", " # tm.start()\n", " \n", " # ephys cable on top\n", " if k == ord('i') and 'cable_motor_port' in cfg['experiment']:\n", " tf = threading.Timer(0, cable_board.turn_CCW, args=[])\n", " tf.start()\n", " if k == ord('o') and 'cable_motor_port' in cfg['experiment']:\n", " tf = threading.Timer(0, cable_board.turn_CW, args=[])\n", " tf.start()\n", " \n", " # feeding\n", " if k == ord('f'):\n", " tf = threading.Timer(0, board.feed, args=[])\n", " tf.start()\n", "\n", " if k == ord('c'):\n", " f_name = cfg_pos['background_light'] if pt.is_light else cfg_pos['background_dark']\n", " cv2.imwrite(f_name, dc.masked_frame)\n", " tf = threading.Timer(0.2, pt.reload_background, args=[])\n", " tf.start() \n", "\n", " # quit the session\n", " if k == ord('q'):\n", " break\n", "\n", " # start the experiment\n", " if k == ord('s'):\n", " board.start_or_stop() # start/stop data acquisition\n", " c_time = time.time() # more accurate time\n", " \n", " if cfg['experiment']['enable_motors']:\n", " if not motor_board.diodes_on:\n", " motor_board.switch_diodes()\n", "\n", " if status.value == 1: # start the session\n", " if t_start is None:\n", " t_start = c_time\n", " trial_start = c_time\n", " status.value = 2\n", " \n", " # init_new_trial\n", " islands = isl_factory.generate_islands(c_time - t_start)\n", " log_islands(islands) # log island(s) positions\n", " sound.value = 1\n", " phase = 1\n", " trial += 1\n", "\n", " # log trial start\n", " tgt = [i for i in islands if not i.is_distractor][0]\n", " log_event(c_time, round(tgt.x, 4), round(tgt.y, 4), round(tgt.r, 4), trial, 0)\n", " \n", " # init light events\n", " timers = []\n", " for event_t in cfg_exp['light_events']:\n", " timers.append(threading.Timer(event_t, switch_light, args=(pt, board)))\n", " \n", " if cfg['experiment']['enable_motors']:\n", " if not cfg['experiment']['phi_max'] == 0 and len(cfg['experiment']['timepoints']) > 0:\n", " direction = False if cfg['experiment']['phi_max'] > 0 else True\n", " t0, t1 = cfg['experiment']['timepoints'][0], cfg['experiment']['timepoints'][1]\n", " timers.append(threading.Timer(t0, motor_board.rotate, args=(direction, abs(cfg['experiment']['phi_max']), t1-t0)))\n", " t0, t1 = cfg['experiment']['timepoints'][2], cfg['experiment']['timepoints'][3]\n", " timers.append(threading.Timer(t0, motor_board.rotate, args=(not direction, abs(cfg['experiment']['phi_max']), t1-t0)))\n", " \n", " for t in timers:\n", " t.start()\n", " \n", " elif status.value == 2: # pause the session\n", " if len(islands) > 0:\n", " tgt = [i for i in islands if not i.is_distractor][0]\n", " x, y, r = round(tgt.x, 4), round(tgt.y, 4), round(tgt.r, 4)\n", " else:\n", " x, y, r = 0, 0, 0\n", " log_event(c_time, x, y, r, trial, -1) # log experiment break\n", " \n", " if 'noise_when_idle' in cfg_exp and cfg_exp['noise_when_idle']:\n", " sound.value = -1\n", " status.value = 1\n", " phase = 0\n", " islands = []\n", " for t in timers:\n", " t.cancel()\n", " \n", " if k == ord('a'):\n", " sound.value = -1 if sound.value >= 0 else 0\n", " switch_light(pt, board) # turn lights off \n", "\n", "finally:\n", " if status.value == 2: # stop data acquisition, in case exited via timeout\n", " board.start_or_stop()\n", " if len(islands) > 0:\n", " tgt = [i for i in islands if not i.is_distractor][0]\n", " x, y, r = round(tgt.x, 4), round(tgt.y, 4), round(tgt.r, 4)\n", " else: \n", " x, y, r = 0, 0, 0\n", " log_event(c_time, x, y, r, trial, -1) # log experiment end\n", " \n", " status.value = 0\n", " time.sleep(0.01)\n", " for t in timers:\n", " t.cancel()\n", " \n", " if board.is_light_off:\n", " board.switch_light() # turn light back on\n", " time.sleep(0.1)\n", " board.exit()\n", " \n", " if cfg['experiment']['enable_motors']:\n", " if motor_board.diodes_on:\n", " motor_board.switch_diodes()\n", " motor_board.exit()\n", " if 'cable_motor_port' in cfg['experiment']:\n", " cable_board.exit()\n", " \n", " cv2.destroyAllWindows()\n", " sc.join()\n", " for ctrl in [pt, vw, vs]:\n", " ctrl.stop()\n", "\n", " # stop microphones recording process\n", " if cfg['microphones']['record_audio']:\n", " mc.join()\n", "\n", " if 'continuous' in cfg['sound']:\n", " time.sleep(cfg['sound']['continuous']['end_sleep'])\n", " cst.stop()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Merge data in HDF5 file" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "session_path = save_to\n", "#trial = 5\n", "#session_path = os.path.join('sessions', '2021-07-30_09-24-14') # some particular session\n", "#session_path = 'Y:\\\\Michael\\\\FreeBehaving\\\\SIT_sessions\\\\51_aSIT_2021-12-03_13-31-51'" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "ename": "IndexError", "evalue": "index 0 is out of bounds for axis 0 with size 0", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mIndexError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;31m# do pack data to HDF5\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mh5name\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mpack\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0msession_path\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[0mtrial\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32mD:\\runSIT\\..\\pipeline\\postprocessing\\pack.py\u001b[0m in \u001b[0;36mpack\u001b[1;34m(session_path)\u001b[0m\n\u001b[0;32m 165\u001b[0m \u001b[1;31m# head direction\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 166\u001b[0m \u001b[0mtemp_tl\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcolumn_stack\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mpos_at_freq\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mx_smooth\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my_smooth\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mspeed\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 167\u001b[1;33m \u001b[0mhd\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mhead_direction\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtemp_tl\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 168\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 169\u001b[0m \u001b[1;31m# trial numbers\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32mD:\\runSIT\\..\\pipeline\\postprocessing\\head_direction.py\u001b[0m in \u001b[0;36mhead_direction\u001b[1;34m(tl, hd_update_speed)\u001b[0m\n\u001b[0;32m 22\u001b[0m \u001b[0mcrit\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwhere\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdiff\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0midle_idxs\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m>\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 23\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 24\u001b[1;33m \u001b[0midle_periods\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0midle_idxs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0midle_idxs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mcrit\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m)\u001b[0m \u001b[1;31m# first idle period\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 25\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mpoint\u001b[0m \u001b[1;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcrit\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 26\u001b[0m \u001b[0midx_start\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0midle_idxs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mcrit\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mIndexError\u001b[0m: index 0 is out of bounds for axis 0 with size 0" ] } ], "source": [ "if not trial > 0:\n", " raise SystemExit('Nothing recorded. No sense to continue.')\n", "\n", "# do pack data to HDF5\n", "h5name = pack(session_path)\n", "trial = 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Plot sessions stats" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import h5py\n", "import numpy as np\n", "from scipy import signal" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "arena_r = 0.4 # in meters\n", "\n", "with h5py.File(h5name, 'r') as f:\n", " tl = np.array(f['processed']['timeline'])\n", " trial_idxs = np.array(f['processed']['trial_idxs'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "fig = plt.figure(figsize=(12, 12))\n", "\n", "# trajectory and islands\n", "ax = fig.add_subplot(221)\n", "ax.scatter(tl[:, 1], tl[:, 2], s=1, alpha=0.1) # positions\n", "scat = ax.scatter(trial_idxs[:, 2], trial_idxs[:, 3], s=1000, facecolors='none', edgecolors='r') # islands, radius approx.\n", "ax.add_patch(plt.Circle((0, 0), arena_r, color='r', fill=False))\n", "ax.set_aspect('equal')\n", "ax.set_xlabel('X, m', fontsize=14)\n", "ax.set_ylabel('Y, m', fontsize=14)\n", "ax.set_title('Running', fontsize=14)\n", "ax.grid()\n", "\n", "# occupancy\n", "sigma = 0.1\n", "lin_profile = np.linspace(-15, 15, 20)\n", "bump = np.exp(-sigma * lin_profile**2)\n", "bump /= np.trapz(bump) # normalize to 1\n", "kernel = bump[:, np.newaxis] * bump[np.newaxis, :]\n", "occupancy_map, _, _ = np.histogram2d(tl[:, 1], tl[:, 2], bins=[40, 40], range=np.array([[-0.5, 0.5], [-0.5, 0.5]]))\n", "occupancy_map = signal.convolve2d(occupancy_map, kernel, mode='same')\n", "\n", "ax = fig.add_subplot(222)\n", "ax.imshow(occupancy_map.T, origin='lower', extent=(-0.5, 0.5, -0.5, 0.5), cmap='Blues')\n", "ax.add_patch(plt.Circle((0, 0), arena_r, color='r', fill=False))\n", "ax.set_xlabel('X, m', fontsize=14)\n", "ax.set_title('Occupancy', fontsize=14)\n", "ax.grid()\n", "\n", "# trials\n", "durations = tl[trial_idxs[:, 1].astype(int)][:, 0] - tl[trial_idxs[:, 0].astype(int)][:, 0]\n", "colors = ['red' if x == 1 else 'grey' for x in trial_idxs[:, 5]]\n", "\n", "ax = fig.add_subplot(223)\n", "ax.barh(np.arange(len(trial_idxs)), durations, color=colors, align='center')\n", "ax.set_xlabel('Time, s', fontsize=14)\n", "ax.set_ylabel('Trial, #', fontsize=14)\n", "ax.set_title('Trials', fontsize=14)\n", "\n", "# speed\n", "ax = fig.add_subplot(224)\n", "\n", "s_rate = 100 # Hz\n", "window = 60 # secs\n", "step = 10 # secs\n", "duration = tl[-1][0]\n", "x_vals = np.arange(int(duration/step))\n", "\n", "inst_speed = [tl[x*step*s_rate:(x*step + window)*s_rate][:, 3].mean() for x in x_vals]\n", "ax.plot(x_vals*step, inst_speed)\n", "ax.set_ylabel('Speed, m/s', fontsize=14)\n", "ax.set_xlabel('Time, s', fontsize=14)\n", "ax.set_title('Speed', fontsize=14)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Performance" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "h5name = os.path.join(session_path, experiment_id + '.h5')\n", "jsname = os.path.join(session_path, experiment_id + '.json')\n", "\n", "# loading position and trial data\n", "with h5py.File(h5name, 'r') as f:\n", " tl = np.array(f['processed']['timeline']) # time, X, Y, speed\n", " trial_idxs = np.array(f['processed']['trial_idxs']) # idx start, idx end, X, Y, R, trial result (idx to tl)\n", "\n", "# loading session configuration\n", "with open(jsname, 'r') as f:\n", " cfg = json.load(f)\n", "\n", "timepoints = cfg['experiment']['timepoints']\n", "s_duration = cfg['experiment']['session_duration']\n", "if len(timepoints) > 0:\n", " periods = [[0, s_duration], [0, timepoints[0]], [timepoints[1], timepoints[2]], [timepoints[3], s_duration]]\n", "else:\n", " periods = [[0, s_duration]]\n", "\n", "# separate ALL, L, D, L'\n", "ds_names = ['performance_ALL', 'performance_L', 'performance_D', 'performance_Lp']\n", "ds_names = ['performance_ALL'] # only light\n", "\n", "for i, ds_name in enumerate(ds_names):\n", " t_start, t_end = periods[i]\n", " trial_starts = tl[trial_idxs[:, 0].astype(np.int32)][:, 0]\n", " trial_ends = tl[trial_idxs[:, 1].astype(np.int32)][:, 0]\n", " tr_idxs = trial_idxs[(trial_starts >= t_start) & (trial_ends <= t_end)]\n", "\n", " dataset = calculate_performance(tl, tr_idxs, cfg)\n", " dump_performance_to_H5(h5name, ds_name, dataset)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(4, 4))\n", "\n", "with h5py.File(h5name, 'r') as f:\n", " perf = np.array(f['analysis']['performance_ALL'])\n", " x = perf[:, 6]\n", "\n", "ax = fig.add_subplot(111)\n", "\n", "ax.plot(x, perf[:, 0]) # performance\n", "ax.plot(x, perf[:, 3]) # chance\n", "ax.fill_between(x, perf[:, 0] + perf[:, 1], perf[:, 0] + perf[:, 2], alpha=0.4)\n", "ax.fill_between(x, perf[:, 3] + perf[:, 4], perf[:, 3] + perf[:, 5], alpha=0.4)\n", "ax.set_ylim(0, 110)\n", "ax.set_xlim(0, 65)\n", "ax.grid()\n", "ax.set_title(experiment_id[-19:], fontsize=14)\n", "ax.set_xlabel('Time, s', fontsize=14)\n", "\n", "if i == 0:\n", " ax.set_ylabel('Successful trials, %', fontsize=14)\n", " \n", "fig.tight_layout()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" } }, "nbformat": 4, "nbformat_minor": 2 }