AllenNeuralDynamics / Aind.Behavior.BciNoMovement

A repository for the aind-bci-no-movement experiment
https://allenneuraldynamics.github.io/Aind.Behavior.BciNoMovement/
MIT License
1 stars 2 forks source link

isometric pull task configuration scheme #19

Open rozmar opened 1 month ago

rozmar commented 1 month ago

We agreed to generate a tiff file for a 2d look-up table to transform loadcell readings to speed, and an additional json file that explains the axes of the tiff file. See output files in this folder + jupyter notebook of the code below: https://drive.google.com/drive/folders/1zrz0l2CxZPtbebJckJIB0vgZYJhVYeR8?usp=sharing

Step by step:

There is an arbitrary function that generates a matrix of force-to-speed LUT:

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
%matplotlib inline
generate a speed matrix
bit_depth = 8 # image bit depth for speed LUT
bin_num_lat = 100 # lateral bin num
bin_num_ap = 100 # anterior-posterior bin num
lat_range = [-100,100] # these are lateral load cell readings
ap_range = [-50,150] # these are anterior-posterior load cell readings
speed_lookup = np.zeros([2**bit_depth,2])
speed_lookup[:,0] = np.arange(2**bit_depth)
speed_lookup[:,1] = np.round(np.arange(-1,1,2/(2**bit_depth))/5,2)*5

lateral_force_vector  =np.arange(lat_range[0],lat_range[1],np.diff(lat_range)/bin_num_lat)
ap_force_vector  =np.arange(ap_range[0],ap_range[1],np.diff(ap_range)/bin_num_ap)

speed_parameters = {'ap':{'center':25,
                                         'width':20},
                                    'lat':{'center':0,
                                         'width':40},
                                    'peak':1,
                                    'trough':-1,
                                    'minimum_value':-.2,
                                    'maximum_value':1}

def gaussian_2d(x_lat,x_ap, 
                              peak,
                              trough,
                              center_lat,
                              center_ap, 
                              sigma_lat, 
                              sigma_ap):
    y =(peak-trough) * np.exp(-((x_lat[np.newaxis,:] - center_lat)**2 / (2 * sigma_lat**2) + (x_ap[:,np.newaxis] - center_ap)**2 / (2 * sigma_ap**2)))
    y= trough+y
    return y

speed_matrix = gaussian_2d(lateral_force_vector,
                           ap_force_vector, 
                           speed_parameters['peak'],
                           speed_parameters['trough'],
                           speed_parameters['lat']['center'],
                           speed_parameters['ap']['center'],
                           speed_parameters['lat']['width'],
                           speed_parameters['ap']['width'])
speed_matrix[speed_matrix<speed_parameters['minimum_value']]=speed_parameters['minimum_value']
speed_matrix[speed_matrix>speed_parameters['maximum_value']]=speed_parameters['maximum_value']

Then we generate a tiff file and a json file (currently 2 versions of json file, one has vectors, one has only scalars, choose whichever you prefer, or request a third one :D)

# create the output files
lut_indices = np.argmin(np.abs((speed_matrix[:,:,np.newaxis]-speed_lookup[:,1][np.newaxis,np.newaxis,:])),2)
im_out = np.asarray(speed_lookup[:,0][lut_indices],int)

dict_out = {'lateral_force_bins':lateral_force_vector.tolist(),
            'ap_force_bins':ap_force_vector.tolist(),
            'speed_lut':speed_lookup.tolist()}

dict_out_scalar = {'lateral_force_min':lat_range[0],
                   'lateral_force_max':lat_range[1],
                   'lateral_bin_num':bin_num_lat,
                   'ap_force_min':ap_range[0],
                   'ap_force_max':ap_range[1],
                   'ap_bin_num':bin_num_lat,
                   'speed_lut_pixel_intensity_min':0,
                   'speed_lut_pixel_intensity_max':2**bit_depth,
                   'speed_lut_speed_min':-1,
                   'speed_lut_speed_max':1,
                   'bit_depth':bit_depth}

#save tiff & json
import json
img = Image.fromarray(np.uint8(im_out))
img.save('/scratch/isometric_force_to_speed.tiff', format='TIFF')
with open('/scratch/isometric_dict_vector.json', 'w') as file:
    json.dump(dict_out, file, indent=4)  # The indent parameter formats it nicely
with open('/scratch/isometric_dict_scalar.json', 'w') as file:
    json.dump(dict_out_scalar, file, indent=4)  # The indent parameter formats it nicely

some plotting:

# plot 

fig = plt.figure(figsize = [18,15])
ax_speed = fig.add_subplot(2,2,1)
im = plt.imshow(speed_matrix,aspect = 'auto',interpolation = 'none',extent = [lat_range[0],lat_range[1],ap_range[1],ap_range[0]])
plt.colorbar(im,label = 'lickport speed towards mouse')
plt.xlabel('lateral force')
plt.ylabel('rostro-caudal force')
plt.title('speed')

ax_image = fig.add_subplot(2,2,2)
im = plt.imshow(im_out,aspect = 'auto',interpolation = 'none')
plt.colorbar(im,label = 'pixel values')
plt.title('tiff image')

ax_speed_lut = fig.add_subplot(2,2,3)
ax_speed_lut.plot(speed_lookup[:,0],speed_lookup[:,1],'k-')
plt.xlabel('pixel intensity')
plt.ylabel('speed towards mouse')
plt.title('speed LUT')

ax_force_scale = fig.add_subplot(2,2,4)
ax_force_scale.plot(lateral_force_vector,label = 'lateral force bins')
ax_force_scale.plot(ap_force_vector,label = 'a-p force bins')
ax_force_scale.legend()
plt.xlabel('pixel index')
plt.ylabel('force bin')
plt.title('force bins for image')

image

How to use:

if using the scalar dictionary, first recreate the vectors:

# if using scalar dict, first generate vectors:

# here regenerating dict_out from dict_out_scalar:

speed_lookup = np.zeros([2**dict_out_scalar['bit_depth'],2])
speed_lookup[:,0] = np.arange(dict_out_scalar['speed_lut_pixel_intensity_min'],dict_out_scalar['speed_lut_pixel_intensity_max'])
speed_lookup[:,1] = np.round(np.arange(dict_out_scalar['speed_lut_speed_min'],dict_out_scalar['speed_lut_speed_max'],2/(2**dict_out_scalar['bit_depth']))/5,2)*5

lateral_force_vector  =np.arange(dict_out_scalar['lateral_force_min'],
                                 dict_out_scalar['lateral_force_max'],
                                 (dict_out_scalar['lateral_force_max']-dict_out_scalar['lateral_force_min'])/dict_out_scalar['lateral_bin_num'])
ap_force_vector  =np.arange(dict_out_scalar['ap_force_min'],
                                 dict_out_scalar['ap_force_max'],
                                 (dict_out_scalar['ap_force_max']-dict_out_scalar['ap_force_min'])/dict_out_scalar['ap_bin_num'])

dict_out = {'lateral_force_bins':lateral_force_vector,
            'ap_force_bins':ap_force_vector,
            'speed_lut':speed_lookup}

Then find the speed:

#usage:
#read lateral and AP force:
force_lateral = 20
force_ap = 35

lateral_idx = np.argmax(force_lateral<=dict_out['lateral_force_bins'])
ap_idx = np.argmax(force_ap<=dict_out['ap_force_bins'])

speed_pixel_int = im_out[ap_idx,lateral_idx]
speed_idx = np.argmax(dict_out['speed_lut'][:,0]>=speed_pixel_int)
speed = dict_out['speed_lut'][speed_idx,1]

print('for lateral force of {} and ap force of {} the needed speed is {}'.format(force_lateral,force_ap,speed))

for lateral force of 20 and ap force of 35 the needed speed is 0.5

rozmar commented 1 month ago

Task description: We define a force-to-speed LUT as above. At trial start the lickport moves to the far position. After trial start the lickport is controlled by the forces exerted on the lever. When the lickport is close to the end position (roughly last 1 mm), a lick triggers reward and the lickport stops. + 2 sec reward consume time If time is up or mouse gets rewarded, new trial starts.