cbpy.pyx 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. '''
  2. Created on March 9, 2013
  3. @author: dashesy
  4. Purpose: Python module for cbsdk_cython
  5. '''
  6. from cbsdk_cython cimport *
  7. from libcpp cimport bool
  8. from libc.stdlib cimport malloc, free
  9. from libc.string cimport strncpy
  10. import sys
  11. import numpy as np
  12. import locale
  13. cimport numpy as np
  14. cimport cython
  15. def version(int instance=0):
  16. '''Get library version
  17. Inputs:
  18. instance - (optional) library instance number
  19. Outputs:"
  20. dictionary with following keys
  21. major - major API version
  22. minor - minor API version
  23. release - release API version
  24. beta - beta API version (0 if a non-beta)
  25. protocol_major - major protocol version
  26. protocol_minor - minor protocol version
  27. nsp_major - major NSP firmware version
  28. nsp_minor - minor NSP firmware version
  29. nsp_release - release NSP firmware version
  30. nsp_beta - beta NSP firmware version (0 if non-beta))
  31. nsp_protocol_major - major NSP protocol version
  32. nsp_protocol_minor - minor NSP protocol version
  33. '''
  34. cdef cbSdkResult res
  35. cdef cbSdkVersion ver
  36. res = cbSdkGetVersion(<uint32_t>instance, &ver)
  37. handle_result(res)
  38. ver_dict = {'major':ver.major, 'minor':ver.minor, 'release':ver.release, 'beta':ver.beta,
  39. 'protocol_major':ver.majorp, 'protocol_minor':ver.majorp,
  40. 'nsp_major':ver.nspmajor, 'nsp_minor':ver.nspminor, 'nsp_release':ver.nsprelease, 'nsp_beta':ver.nspbeta,
  41. 'nsp_protocol_major':ver.nspmajorp, 'nsp_protocol_minor':ver.nspmajorp
  42. }
  43. return <int>res, ver_dict
  44. def defaultConParams():
  45. #Note: Defaulting to 255.255.255.255 assumes the client is connected to the NSP via a switch.
  46. #A direct connection might require the client-addr to be "192.168.137.1"
  47. con_parms = {
  48. 'client-addr': str(cbNET_UDP_ADDR_BCAST.decode("utf-8"))\
  49. if ('linux' in sys.platform or 'linux2' in sys.platform) else '255.255.255.255',
  50. 'client-port': cbNET_UDP_PORT_BCAST,
  51. 'inst-addr': cbNET_UDP_ADDR_CNT.decode("utf-8"),
  52. 'inst-port': cbNET_UDP_PORT_CNT,
  53. 'receive-buffer-size': (8 * 1024 * 1024) if sys.platform == 'win32' else (6 * 1024 * 1024)
  54. }
  55. return con_parms
  56. def open(int instance=0, connection='default', parameter={}):
  57. '''Open library.
  58. Inputs:
  59. connection - connection type, string can be one the following
  60. 'default': tries slave then master connection
  61. 'master': tries master connection (UDP)
  62. 'slave': tries slave connection (needs another master already open)
  63. parameter - dictionary with following keys (all optional)
  64. 'inst-addr': instrument IPv4 address.
  65. 'inst-port': instrument port number.
  66. 'client-addr': client IPv4 address.
  67. 'client-port': client port number.
  68. 'receive-buffer-size': override default network buffer size (low value may result in drops).
  69. instance - (optional) library instance number
  70. Outputs:
  71. Same as "get_connection_type" command output
  72. Test with:
  73. from cerebus import cbpy
  74. cbpy.open(parameter=cbpy.defaultConParams())
  75. '''
  76. cdef cbSdkResult res
  77. wconType = {'default': CBSDKCONNECTION_DEFAULT, 'slave': CBSDKCONNECTION_CENTRAL, 'master': CBSDKCONNECTION_UDP}
  78. if not connection in wconType.keys():
  79. raise RuntimeError("invalid connection %s" % connection)
  80. cdef cbSdkConnectionType conType = wconType[connection]
  81. cdef cbSdkConnection con
  82. cdef bytes szOutIP = parameter.get('inst-addr', cbNET_UDP_ADDR_CNT.decode("utf-8")).encode()
  83. cdef bytes szInIP = parameter.get('client-addr', '').encode()
  84. con.szOutIP = szOutIP
  85. con.nOutPort = parameter.get('inst-port', cbNET_UDP_PORT_CNT)
  86. con.szInIP = szInIP
  87. con.nInPort = parameter.get('client-port', cbNET_UDP_PORT_BCAST)
  88. con.nRecBufSize = parameter.get('receive-buffer-size', 0)
  89. res = cbSdkOpen(<uint32_t>instance, conType, con)
  90. handle_result(res)
  91. return <int>res, get_connection_type(instance=instance)
  92. def close(int instance=0):
  93. '''Close library.
  94. Inputs:
  95. instance - (optional) library instance number
  96. '''
  97. return handle_result(cbSdkClose(<uint32_t>instance))
  98. def get_connection_type(int instance=0):
  99. ''' Get connection type
  100. Inputs:
  101. instance - (optional) library instance number
  102. Outputs:
  103. dictionary with following keys
  104. 'connection': Final established connection; can be any of:
  105. 'Default', 'Slave', 'Master', 'Closed', 'Unknown'
  106. 'instrument': Instrument connected to; can be any of:
  107. 'NSP', 'nPlay', 'Local NSP', 'Remote nPlay', 'Unknown')
  108. '''
  109. cdef cbSdkResult res
  110. cdef cbSdkConnectionType conType
  111. cdef cbSdkInstrumentType instType
  112. res = cbSdkGetType(<uint32_t>instance, &conType, &instType)
  113. handle_result(res)
  114. connections = ["Default", "Slave", "Master", "Closed", "Unknown"]
  115. instruments = ["NSP", "nPlay", "Local NSP", "Remote nPlay", "Unknown"]
  116. con_idx = conType
  117. if con_idx < 0 or con_idx >= len(connections):
  118. con_idx = len(connections) - 1
  119. inst_idx = instType
  120. if inst_idx < 0 or inst_idx >= len(instruments):
  121. inst_idx = len(instruments) - 1
  122. return {'connection':connections[con_idx], 'instrument':instruments[inst_idx]}
  123. def trial_config(int instance=0, reset=True,
  124. buffer_parameter={},
  125. range_parameter={},
  126. noevent=0, nocontinuous=0, nocomment=0):
  127. '''Configure trial settings.
  128. Inputs:
  129. reset - boolean, set True to flush data cache and start collecting data immediately,
  130. set False to stop collecting data immediately
  131. buffer_parameter - (optional) dictionary with following keys (all optional)
  132. 'double': boolean, if specified, the data is in double precision format
  133. 'absolute': boolean, if specified event timing is absolute (new polling will not reset time for events)
  134. 'continuous_length': set the number of continuous data to be cached
  135. 'event_length': set the number of events to be cached
  136. 'comment_length': set number of comments to be cached
  137. 'tracking_length': set the number of video tracking events to be cached
  138. range_parameter - (optional) dictionary with following keys (all optional)
  139. 'begin_channel': integer, channel to start polling if certain value seen
  140. 'begin_mask': integer, channel mask to start polling if certain value seen
  141. 'begin_value': value to start polling
  142. 'end_channel': channel to end polling if certain value seen
  143. 'end_mask': channel mask to end polling if certain value seen
  144. 'end_value': value to end polling
  145. 'noevent': equivalent of setting buffer_parameter['event_length'] to 0
  146. 'nocontinuous': equivalent of setting buffer_parameter['continuous_length'] to 0
  147. 'nocomment': equivalent of setting buffer_parameter['comment_length'] to 0
  148. instance - (optional) library instance number
  149. Outputs:
  150. reset - (boolean) if it is reset
  151. '''
  152. cdef cbSdkResult res
  153. cdef cbSdkConfigParam cfg_param
  154. # retrieve old values
  155. res = cbsdk_get_trial_config(<uint32_t>instance, &cfg_param)
  156. handle_result(res)
  157. cfg_param.bActive = <uint32_t>reset
  158. # Fill cfg_param with provided buffer_parameter values or default.
  159. cfg_param.bDouble = buffer_parameter.get('double', cfg_param.bDouble)
  160. cfg_param.uWaveforms = 0 # does not work anyways
  161. cfg_param.uConts = 0 if nocontinuous else buffer_parameter.get('continuous_length', cbSdk_CONTINUOUS_DATA_SAMPLES)
  162. cfg_param.uEvents = 0 if noevent else buffer_parameter.get('event_length', cbSdk_EVENT_DATA_SAMPLES)
  163. cfg_param.uComments = 0 if nocomment else buffer_parameter.get('comment_length', 0)
  164. cfg_param.uTrackings = buffer_parameter.get('tracking_length', 0)
  165. cfg_param.bAbsolute = buffer_parameter.get('absolute', 0)
  166. # Fill cfg_param mask-related parameters with provided range_parameter or default.
  167. cfg_param.Begchan = range_parameter.get('begin_channel', 0)
  168. cfg_param.Begmask = range_parameter.get('begin_mask', 0)
  169. cfg_param.Begval = range_parameter.get('begin_value', 0)
  170. cfg_param.Endchan = range_parameter.get('end_channel', 0)
  171. cfg_param.Endmask = range_parameter.get('end_mask', 0)
  172. cfg_param.Endval = range_parameter.get('end_value', 0)
  173. res = cbsdk_set_trial_config(<int>instance, &cfg_param)
  174. handle_result(res)
  175. return <int>res, reset
  176. def trial_event(int instance=0, bool reset=False):
  177. ''' Trial spike and event data.
  178. Inputs:
  179. reset - (optional) boolean
  180. set False (default) to leave buffer intact.
  181. set True to clear all the data and reset the trial time to the current time.
  182. instance - (optional) library instance number
  183. Outputs:
  184. list of arrays [channel, {'timestamps':[unit0_ts, ..., unitN_ts], 'events':digital_events}]
  185. channel: integer, channel number (1-based)
  186. digital_events: array, digital event values for channel (if a digital or serial channel)
  187. unitN_ts: array, spike timestamps of unit N for channel (if an electrode channel));
  188. '''
  189. cdef cbSdkResult res
  190. cdef cbSdkConfigParam cfg_param
  191. cdef cbSdkTrialEvent trialevent
  192. trial = []
  193. # retrieve old values
  194. res = cbsdk_get_trial_config(<uint32_t>instance, &cfg_param)
  195. handle_result(res)
  196. # get how many samples are available
  197. res = cbsdk_init_trial_event(<uint32_t>instance, <int>reset, &trialevent)
  198. handle_result(res)
  199. if trialevent.count == 0:
  200. return res, trial
  201. cdef np.double_t[:] mxa_d
  202. cdef np.uint32_t[:] mxa_u32
  203. cdef np.uint16_t[:] mxa_u16
  204. # allocate memory
  205. for channel in range(trialevent.count):
  206. ch = trialevent.chan[channel] # Actual channel number
  207. timestamps = []
  208. # Fill timestamps for non-empty channels
  209. for u in range(cbMAXUNITS+1):
  210. trialevent.timestamps[channel][u] = NULL
  211. num_samples = trialevent.num_samples[channel][u]
  212. ts = []
  213. if num_samples:
  214. if cfg_param.bDouble:
  215. mxa_d = np.zeros(num_samples, dtype=np.double)
  216. trialevent.timestamps[channel][u] = <void *>&mxa_d[0]
  217. ts = np.asarray(mxa_d)
  218. else:
  219. mxa_u32 = np.zeros(num_samples, dtype=np.uint32)
  220. trialevent.timestamps[channel][u] = <void *>&mxa_u32[0]
  221. ts = np.asarray(mxa_u32)
  222. timestamps.append(ts)
  223. trialevent.waveforms[channel] = NULL
  224. dig_events = []
  225. # Fill values for non-empty digital or serial channels
  226. if (ch == (cbNUM_ANALOG_CHANS + cbNUM_ANALOGOUT_CHANS + cbNUM_DIGIN_CHANS))\
  227. or (ch == (cbNUM_ANALOG_CHANS + cbNUM_ANALOGOUT_CHANS + cbNUM_DIGIN_CHANS + cbNUM_SERIAL_CHANS)):
  228. num_samples = trialevent.num_samples[channel][0]
  229. if num_samples:
  230. if cfg_param.bDouble:
  231. mxa_d = np.zeros(num_samples, dtype=np.double)
  232. trialevent.waveforms[channel] = <void *>&mxa_d[0]
  233. dig_events = np.asarray(mxa_d)
  234. else:
  235. mxa_u16 = np.zeros(num_samples, dtype=np.uint16)
  236. trialevent.waveforms[channel] = <void *>&mxa_u16[0]
  237. dig_events = np.asarray(mxa_u16)
  238. trial.append([ch, {'timestamps':timestamps, 'events':dig_events}])
  239. # get the trial
  240. res = cbsdk_get_trial_event(<uint32_t>instance, <int>reset, &trialevent)
  241. handle_result(res)
  242. return <int>res, trial
  243. def trial_continuous(int instance=0, bool reset=False):
  244. ''' Trial continuous data.
  245. Inputs:
  246. reset - (optional) boolean
  247. set False (default) to leave buffer intact.
  248. set True to clear all the data and reset the trial time to the current time.
  249. instance - (optional) library instance number
  250. Outputs:
  251. list of the form [channel, continuous_array, sample_rate]
  252. channel: integer, channel number (1-based)
  253. continuous_array: array, continuous values for channel)
  254. sample_rate: integer, sampling rate at which data were acquired
  255. '''
  256. cdef cbSdkResult res
  257. cdef cbSdkConfigParam cfg_param
  258. cdef cbSdkTrialCont trialcont
  259. trial = []
  260. # retrieve old values
  261. res = cbsdk_get_trial_config(<uint32_t>instance, &cfg_param)
  262. handle_result(res)
  263. # get how many samples are available
  264. res = cbsdk_init_trial_cont(<uint32_t>instance, <int>reset, &trialcont)
  265. handle_result(res)
  266. if trialcont.count == 0:
  267. return res, trial, trialcont.time
  268. cdef np.double_t[:] mxa_d
  269. cdef np.int16_t[:] mxa_i16
  270. # allocate memory
  271. for channel in range(trialcont.count):
  272. ch = trialcont.chan[channel] # Actual channel number
  273. row = [ch]
  274. trialcont.samples[channel] = NULL
  275. num_samples = trialcont.num_samples[channel]
  276. if cfg_param.bDouble:
  277. mxa_d = np.zeros(num_samples, dtype=np.double)
  278. if num_samples:
  279. trialcont.samples[channel] = <void *>&mxa_d[0]
  280. cont = np.asarray(mxa_d)
  281. else:
  282. mxa_i16 = np.zeros(num_samples, dtype=np.int16)
  283. if num_samples:
  284. trialcont.samples[channel] = <void *>&mxa_i16[0]
  285. cont = np.asarray(mxa_i16)
  286. row.append(cont)
  287. row.append(trialcont.sample_rates[channel])
  288. trial.append(row)
  289. # get the trial
  290. res = cbsdk_get_trial_cont(<uint32_t>instance, <int>reset, &trialcont)
  291. handle_result(res)
  292. return <int>res, trial, trialcont.time
  293. def trial_data(int instance=0, bool reset=False):
  294. '''
  295. :param instance: (optional) library instance number
  296. :param reset: (optional) boolean
  297. set False (default) to leave buffer intact.
  298. set True to clear all the data and reset the trial time to the current time.
  299. :return:
  300. res: (int) returned by cbsdk
  301. continuous data: list of the form [channel, continuous_array]
  302. channel: integer, channel number (1-based)
  303. continuous_array: array, continuous values for channel)
  304. event data: list of arrays [channel, {'timestamps':[unit0_ts, ..., unitN_ts], 'events':digital_events}]
  305. channel: integer, channel number (1-based)
  306. digital_events: array, digital event values for channel (if a digital or serial channel)
  307. unitN_ts: array, spike timestamps of unit N for channel (if an electrode channel));
  308. '''
  309. cdef cbSdkResult res
  310. cdef cbSdkConfigParam cfg_param
  311. cdef cbSdkTrialCont trialcont
  312. cdef cbSdkTrialEvent trialevent
  313. trial_event = []
  314. trial_cont = []
  315. # retrieve old values
  316. res = cbsdk_get_trial_config(<uint32_t>instance, &cfg_param)
  317. handle_result(res)
  318. # get how many samples are available
  319. res = cbsdk_init_trial_data(<uint32_t>instance, <int>reset, &trialevent, &trialcont)
  320. handle_result(res)
  321. if trialevent.count == 0 or trialcont.count == 0:
  322. return res, trial_event, trial_cont
  323. cdef np.double_t[:] mxa_d_event, mxa_d_cont
  324. cdef np.int16_t[:] mxa_i16
  325. cdef np.uint32_t[:] mxa_u32
  326. cdef np.uint16_t[:] mxa_u16
  327. # allocate memory for trial event
  328. for channel in range(trialevent.count):
  329. ch = trialevent.chan[channel] # Actual channel number
  330. timestamps = []
  331. # Fill timestamps for non-empty channels
  332. for u in range(cbMAXUNITS+1):
  333. trialevent.timestamps[channel][u] = NULL
  334. num_samples = trialevent.num_samples[channel][u]
  335. ts = []
  336. if num_samples:
  337. if cfg_param.bDouble:
  338. mxa_d_event = np.zeros(num_samples, dtype=np.double)
  339. trialevent.timestamps[channel][u] = <void *>&mxa_d_event[0]
  340. ts = np.asarray(mxa_d_event)
  341. else:
  342. mxa_u32 = np.zeros(num_samples, dtype=np.uint32)
  343. trialevent.timestamps[channel][u] = <void *>&mxa_u32[0]
  344. ts = np.asarray(mxa_u32)
  345. timestamps.append(ts)
  346. trialevent.waveforms[channel] = NULL
  347. dig_events = []
  348. # Fill values for non-empty digital or serial channels
  349. if (ch == (cbNUM_ANALOG_CHANS + cbNUM_ANALOGOUT_CHANS + cbNUM_DIGIN_CHANS))\
  350. or (ch == (cbNUM_ANALOG_CHANS + cbNUM_ANALOGOUT_CHANS + cbNUM_DIGIN_CHANS + cbNUM_SERIAL_CHANS)):
  351. num_samples = trialevent.num_samples[channel][0]
  352. if num_samples:
  353. if cfg_param.bDouble:
  354. mxa_d_event = np.zeros(num_samples, dtype=np.double)
  355. trialevent.waveforms[channel] = <void *>&mxa_d_event[0]
  356. dig_events = np.asarray(mxa_d_event)
  357. else:
  358. mxa_u16 = np.zeros(num_samples, dtype=np.uint16)
  359. trialevent.waveforms[channel] = <void *>&mxa_u16[0]
  360. dig_events = np.asarray(mxa_u16)
  361. trial_event.append([ch, {'timestamps':timestamps, 'events':dig_events}])
  362. # allocate memory for trial continuous
  363. for channel in range(trialcont.count):
  364. ch = trialcont.chan[channel] # Actual channel number
  365. row = [ch]
  366. trialcont.samples[channel] = NULL
  367. num_samples = trialcont.num_samples[channel]
  368. if cfg_param.bDouble:
  369. mxa_d_cont = np.zeros(num_samples, dtype=np.double)
  370. if num_samples:
  371. trialcont.samples[channel] = <void *>&mxa_d_cont[0]
  372. cont = np.asarray(mxa_d_cont)
  373. else:
  374. mxa_i16 = np.zeros(num_samples, dtype=np.int16)
  375. if num_samples:
  376. trialcont.samples[channel] = <void *>&mxa_i16[0]
  377. cont = np.asarray(mxa_i16)
  378. row.append(cont)
  379. trial_cont.append(row)
  380. # cbsdk get trial data
  381. res = cbsdk_get_trial_data(<uint32_t>instance, <int>reset, &trialevent, &trialcont)
  382. handle_result(res)
  383. return <int>res, trial_event, trial_cont, trialcont.time
  384. def trial_comment(int instance=0, bool reset=False):
  385. ''' Trial comment data.
  386. Inputs:
  387. reset - (optional) boolean
  388. set False (default) to leave buffer intact.
  389. set True to clear all the data and reset the trial time to the current time.
  390. instance - (optional) library instance number
  391. Outputs:
  392. list of lists the form [timestamp, comment_str, rgba]
  393. timestamp: ?
  394. comment_str: the comment as a py string
  395. rgba: integer; the comment colour. 8 bits each for r, g, b, a
  396. '''
  397. cdef cbSdkResult res
  398. cdef cbSdkConfigParam cfg_param
  399. cdef cbSdkTrialComment trialcomm
  400. # retrieve old values
  401. res = cbsdk_get_trial_config(<uint32_t>instance, &cfg_param)
  402. handle_result(<cbSdkResult>res)
  403. # get how many comments are available
  404. res = cbsdk_init_trial_comment(<uint32_t>instance, <int>reset, &trialcomm)
  405. handle_result(res)
  406. if trialcomm.num_samples == 0:
  407. return <int>res, []
  408. # allocate memory
  409. # types
  410. cdef np.double_t[:] mxa_d
  411. cdef np.uint8_t[:] mxa_u8
  412. cdef np.uint32_t[:] mxa_u32
  413. # For charsets;
  414. mxa_u8 = np.zeros(trialcomm.num_samples, dtype=np.uint8)
  415. trialcomm.charsets = <uint8_t *>&mxa_u8[0]
  416. my_charsets = np.asarray(mxa_u8)
  417. # For rgbas
  418. mxa_u32 = np.zeros(trialcomm.num_samples, dtype=np.uint32)
  419. trialcomm.rgbas = <uint32_t *>&mxa_u32[0]
  420. my_rgbas = np.asarray(mxa_u32)
  421. # For comments
  422. trialcomm.comments = <uint8_t **>malloc(trialcomm.num_samples * sizeof(uint8_t*))
  423. for comm_ix in range(trialcomm.num_samples):
  424. trialcomm.comments[comm_ix] = <uint8_t *>malloc(256 * sizeof(uint8_t))
  425. # For timestamps
  426. if cfg_param.bDouble:
  427. mxa_d = np.zeros(trialcomm.num_samples, dtype=np.double)
  428. trialcomm.timestamps = <void *>&mxa_d[0]
  429. my_timestamps = np.asarray(mxa_d)
  430. else:
  431. mxa_u32 = np.zeros(trialcomm.num_samples, dtype=np.uint32)
  432. trialcomm.timestamps = <void *>&mxa_u32[0]
  433. my_timestamps = np.asarray(mxa_u32)
  434. try:
  435. res = cbsdk_get_trial_comment(<int>instance, <int>reset, &trialcomm)
  436. handle_result(res)
  437. trial = []
  438. for comm_ix in range(trialcomm.num_samples):
  439. this_enc = 'utf-16' if my_charsets[comm_ix]==1 else locale.getpreferredencoding()
  440. row = [my_timestamps[comm_ix], trialcomm.comments[comm_ix].decode(this_enc), my_rgbas[comm_ix]]
  441. trial.append(row)
  442. return <int>res, trial
  443. finally:
  444. free(trialcomm.comments)
  445. def file_config(int instance=0, command='info', comment='', filename=''):
  446. ''' Configure remote file recording or get status of recording.
  447. Inputs:
  448. command - string, File configuration command, can be of of the following
  449. 'info': (default) get File recording information
  450. 'open': opens the File dialog if closed, ignoring other parameters
  451. 'close': closes the File dialog if open
  452. 'start': starts recording, opens dialog if closed
  453. 'stop': stops recording
  454. filename - (optional) string, file name to use for recording
  455. comment - (optional) string, file comment to use for file recording
  456. instance - (optional) library instance number
  457. Outputs:
  458. Only if command is 'info' output is returned
  459. A dictionary with following keys:
  460. 'Recording': boolean, if recording is in progress
  461. 'FileName': string, file name being recorded
  462. 'UserName': Computer that is recording
  463. '''
  464. cdef cbSdkResult res
  465. cdef char fname[256]
  466. cdef char username[256]
  467. cdef bool bRecording = False
  468. if command == 'info':
  469. res = cbSdkGetFileConfig(<uint32_t>instance, fname, username, &bRecording)
  470. handle_result(res)
  471. info = {'Recording':bRecording, 'FileName':<bytes>fname, 'UserName':<bytes>username}
  472. return res, info
  473. cdef int start = 0
  474. cdef unsigned int options = cbFILECFG_OPT_NONE
  475. if command == 'open':
  476. if filename or comment:
  477. raise RuntimeError('filename and comment should not be specified for open')
  478. options = cbFILECFG_OPT_OPEN
  479. elif command == 'close':
  480. options = cbFILECFG_OPT_CLOSE
  481. elif command == 'start':
  482. if not filename:
  483. raise RuntimeError('filename should be specified for start')
  484. start = 1
  485. elif command == 'stop':
  486. if not filename:
  487. raise RuntimeError('filename should be specified for stop')
  488. start = 0
  489. else:
  490. raise RuntimeError("invalid file config command %s" % command)
  491. cdef int set_res
  492. filename_string = filename.encode('UTF-8')
  493. comment_string = comment.encode('UTF-8')
  494. set_res = cbsdk_file_config(<uint32_t>instance, <const char *>filename_string, <char *>comment_string, start, options)
  495. return set_res
  496. def time(int instance=0, unit='samples'):
  497. '''Instrument time.
  498. Inputs:
  499. unit - time unit, string can be one the following
  500. 'samples': (default) sample number integer
  501. 'seconds' or 's': seconds calculated from samples
  502. 'milliseconds' or 'ms': milliseconds calculated from samples
  503. instance - (optional) library instance number
  504. Outputs:
  505. time - time passed since last reset
  506. '''
  507. cdef cbSdkResult res
  508. if unit == 'samples':
  509. factor = 1
  510. elif unit in ['seconds', 's']:
  511. raise NotImplementedError("Use time unit of samples for now")
  512. elif unit in ['milliseconds', 'ms']:
  513. raise NotImplementedError("Use time unit of samples for now")
  514. else:
  515. raise RuntimeError("Invalid time unit %s" % unit)
  516. cdef uint32_t cbtime
  517. res = cbSdkGetTime(<uint32_t>instance, &cbtime)
  518. handle_result(res)
  519. time = float(cbtime) / factor
  520. return <int>res, time
  521. def analog_out(channel_out, channel_mon, track_last=True, spike_only=False, int instance=0):
  522. '''
  523. Monitor a channel.
  524. Inputs:
  525. channel_out - integer, analog output channel number (1-based)
  526. On NSP, should be >= MIN_CHANS_ANALOG_OUT (145) && <= MAX_CHANS_AUDIO (150)
  527. channel_mon - integer, channel to monitor (1-based)
  528. track_last - (optional) If True, track last channel clicked on in raster plot or hardware config window.
  529. spike_only - (optional) If True, only play spikes. If False, play continuous.
  530. '''
  531. cdef cbSdkResult res
  532. cdef cbSdkAoutMon mon
  533. if channel_mon is None:
  534. res = cbSdkSetAnalogOutput(<uint32_t>instance, <uint16_t>channel_out, NULL, NULL)
  535. else:
  536. mon.chan = <uint16_t>channel_mon
  537. mon.bTrack = track_last
  538. mon.bSpike = spike_only
  539. res = cbSdkSetAnalogOutput(<uint32_t>instance, <uint16_t>channel_out, NULL, &mon)
  540. handle_result(res)
  541. return <int>res
  542. def digital_out(int channel, int instance=0, value='low'):
  543. '''Digital output command.
  544. Inputs:
  545. channel - integer, digital output channel number (1-based)
  546. On NSP, 153 (dout1), 154 (dout2), 155 (dout3), 156 (dout4)
  547. value - (optional), depends on the command
  548. for command of 'set_value':
  549. string, can be 'high' or 'low' (default)
  550. instance - (optional) library instance number'''
  551. values = ['low', 'high']
  552. if value not in values:
  553. raise RuntimeError("Invalid value %s" % value)
  554. cdef cbSdkResult res
  555. cdef uint16_t int_val = values.index(value)
  556. res = cbSdkSetDigitalOutput(<uint32_t>instance, <uint16_t>channel, int_val)
  557. handle_result(res)
  558. return <int>res
  559. def get_channel_config(int channel, int instance=0):
  560. '''
  561. Outputs:
  562. -chaninfo = A Python dictionary with the following fields:
  563. 'time': system clock timestamp,
  564. 'chid': 0x8000,
  565. 'type': cbPKTTYPE_AINP*,
  566. 'dlen': cbPKT_DLENCHANINFO,
  567. 'chan': actual channel id of the channel being configured,
  568. 'proc': the address of the processor on which the channel resides,
  569. 'bank': the address of the bank on which the channel resides,
  570. 'term': the terminal number of the channel within it's bank,
  571. 'chancaps': general channel capablities (given by cbCHAN_* flags),
  572. 'doutcaps': digital output capablities (composed of cbDOUT_* flags),
  573. 'dinpcaps': digital input capablities (composed of cbDINP_* flags),
  574. 'aoutcaps': analog output capablities (composed of cbAOUT_* flags),
  575. 'ainpcaps': analog input capablities (composed of cbAINP_* flags),
  576. 'spkcaps': spike processing capabilities,
  577. 'label': Label of the channel (null terminated if <16 characters),
  578. 'userflags': User flags for the channel state,
  579. 'doutopts': digital output options (composed of cbDOUT_* flags),
  580. 'dinpopts': digital input options (composed of cbDINP_* flags),
  581. 'aoutopts': analog output options,
  582. 'eopchar': digital input capablities (given by cbDINP_* flags),
  583. 'ainpopts': analog input options (composed of cbAINP* flags),
  584. 'smpfilter': continuous-time pathway filter id,
  585. 'smpgroup': continuous-time pathway sample group,
  586. 'smpdispmin': continuous-time pathway display factor,
  587. 'smpdispmax': continuous-time pathway display factor,
  588. 'trigtype': trigger type (see cbDOUT_TRIGGER_*),
  589. 'trigchan': trigger channel,
  590. 'trigval': trigger value,
  591. 'lncrate': line noise cancellation filter adaptation rate,
  592. 'spkfilter': spike pathway filter id,
  593. 'spkdispmax': spike pathway display factor,
  594. 'lncdispmax': Line Noise pathway display factor,
  595. 'spkopts': spike processing options,
  596. 'spkthrlevel': spike threshold level,
  597. 'spkthrlimit': ,
  598. 'spkgroup': NTrodeGroup this electrode belongs to - 0 is single unit, non-0 indicates a multi-trode grouping,
  599. 'amplrejpos': Amplitude rejection positive value,
  600. 'amplrejneg': Amplitude rejection negative value,
  601. 'refelecchan': Software reference electrode channel,
  602. '''
  603. cdef cbSdkResult res
  604. cdef cbPKT_CHANINFO cb_chaninfo
  605. res = cbSdkGetChannelConfig(<uint32_t>instance, <uint16_t>channel, &cb_chaninfo)
  606. handle_result(res)
  607. if res != 0:
  608. return <int>res, {}
  609. chaninfo = {
  610. 'time': cb_chaninfo.time,
  611. 'chid': cb_chaninfo.chid,
  612. 'type': cb_chaninfo.type, # cbPKTTYPE_AINP*
  613. 'dlen': cb_chaninfo.dlen, # cbPKT_DLENCHANINFO
  614. 'chan': cb_chaninfo.chan,
  615. 'proc': cb_chaninfo.proc,
  616. 'bank': cb_chaninfo.bank,
  617. 'term': cb_chaninfo.term,
  618. 'chancaps': cb_chaninfo.chancaps,
  619. 'doutcaps': cb_chaninfo.doutcaps,
  620. 'dinpcaps': cb_chaninfo.dinpcaps,
  621. 'aoutcaps': cb_chaninfo.aoutcaps,
  622. 'ainpcaps': cb_chaninfo.ainpcaps,
  623. 'spkcaps': cb_chaninfo.spkcaps,
  624. 'label': cb_chaninfo.label.decode('utf-8'),
  625. 'userflags': cb_chaninfo.userflags,
  626. 'doutopts': cb_chaninfo.doutopts,
  627. 'dinpopts': cb_chaninfo.dinpopts,
  628. 'aoutopts': cb_chaninfo.aoutopts,
  629. 'eopchar': cb_chaninfo.eopchar,
  630. 'monsource': cb_chaninfo.monsource,
  631. 'outvalue': cb_chaninfo.outvalue,
  632. 'aoutopts': cb_chaninfo.aoutopts,
  633. 'eopchar': cb_chaninfo.eopchar,
  634. 'ainpopts': cb_chaninfo.ainpopts,
  635. 'smpfilter': cb_chaninfo.smpfilter,
  636. 'smpgroup': cb_chaninfo.smpgroup,
  637. 'smpdispmin': cb_chaninfo.smpdispmin,
  638. 'smpdispmax': cb_chaninfo.smpdispmax,
  639. 'trigtype': cb_chaninfo.trigtype,
  640. 'trigchan': cb_chaninfo.trigchan,
  641. 'lncrate': cb_chaninfo.lncrate,
  642. 'spkfilter': cb_chaninfo.spkfilter,
  643. 'spkdispmax': cb_chaninfo.spkdispmax,
  644. 'lncdispmax': cb_chaninfo.lncdispmax,
  645. 'spkopts': cb_chaninfo.spkopts,
  646. 'spkthrlevel': cb_chaninfo.spkthrlevel,
  647. 'spkthrlimit': cb_chaninfo.spkthrlimit,
  648. 'spkgroup': cb_chaninfo.spkgroup,
  649. 'amplrejpos': cb_chaninfo.amplrejpos,
  650. 'amplrejneg': cb_chaninfo.amplrejneg,
  651. 'refelecchan': cb_chaninfo.refelecchan
  652. }
  653. # TODO:
  654. #cbSCALING physcalin # physical channel scaling information
  655. #cbFILTDESC phyfiltin # physical channel filter definition
  656. #cbSCALING physcalout # physical channel scaling information
  657. #cbFILTDESC phyfiltout # physical channel filter definition
  658. #int32_t position[4] # reserved for future position information
  659. #cbSCALING scalin # user-defined scaling information for AINP
  660. #cbSCALING scalout # user-defined scaling information for AOUT
  661. #uint32_t monsource
  662. #int32_t outvalue # output value
  663. #uint16_t lowsamples # address of channel to monitor
  664. #uint16_t highsamples # address of channel to monitor
  665. #int32_t offset
  666. #cbMANUALUNITMAPPING unitmapping[cbMAXUNITS+0] # manual unit mapping
  667. #cbHOOP spkhoops[cbMAXUNITS+0][cbMAXHOOPS+0] # spike hoop sorting set
  668. return <int>res, chaninfo
  669. def set_channel_config(int channel, chaninfo={}, int instance=0):
  670. """
  671. Inputs:
  672. chaninfo: A Python dict. See fields descriptions in get_channel_config. All fields are optional.
  673. """
  674. cdef cbSdkResult res
  675. cdef cbPKT_CHANINFO cb_chaninfo
  676. res = cbSdkGetChannelConfig(<uint32_t>instance, <uint16_t>channel, &cb_chaninfo)
  677. handle_result(res)
  678. if 'label' in chaninfo:
  679. new_label = chaninfo['label']
  680. if not isinstance(new_label, bytes):
  681. new_label = new_label.encode()
  682. strncpy(cb_chaninfo.label, new_label, sizeof(new_label))
  683. if 'smpgroup' in chaninfo:
  684. cb_chaninfo.smpgroup = chaninfo['smpgroup']
  685. if 'spkthrlevel' in chaninfo:
  686. cb_chaninfo.spkthrlevel = chaninfo['spkthrlevel']
  687. res = cbSdkSetChannelConfig(<uint32_t>instance, <uint16_t>channel, &cb_chaninfo)
  688. handle_result(res)
  689. return <int>res
  690. def get_sample_group(int group_ix, int instance=0):
  691. """
  692. """
  693. cdef cbSdkResult res
  694. cdef uint32_t proc = 1
  695. cdef uint32_t nChansInGroup
  696. res = cbSdkGetSampleGroupList(<uint32_t>instance, proc, group_ix, &nChansInGroup, NULL)
  697. handle_result(res)
  698. if (nChansInGroup <= 0):
  699. return <int> res, []
  700. cdef uint32_t pGroupList[cbNUM_ANALOG_CHANS+0]
  701. res = cbSdkGetSampleGroupList(<uint32_t>instance, proc, <uint32_t>group_ix, &nChansInGroup, pGroupList)
  702. handle_result(res)
  703. cdef cbPKT_CHANINFO chanInfo
  704. channels_info = []
  705. for chan_ix in range(nChansInGroup):
  706. chan_info = {}
  707. res = cbSdkGetChannelConfig(<uint32_t>instance, pGroupList[chan_ix], &chanInfo)
  708. handle_result(<cbSdkResult>res)
  709. anaRange = chanInfo.physcalin.anamax - chanInfo.physcalin.anamin
  710. digRange = chanInfo.physcalin.digmax - chanInfo.physcalin.digmin
  711. chan_info['chid'] = chanInfo.chid
  712. chan_info['chan'] = chanInfo.chan
  713. chan_info['proc'] = chanInfo.proc
  714. chan_info['bank'] = chanInfo.bank
  715. chan_info['term'] = chanInfo.term
  716. chan_info['gain'] = anaRange / digRange
  717. chan_info['label'] = chanInfo.label
  718. chan_info['unit'] = chanInfo.physcalin.anaunit
  719. channels_info.append(chan_info)
  720. return <int>res, channels_info
  721. def set_comment(comment_string, rgba_tuple=(0, 0, 0, 255), int instance=0):
  722. cdef cbSdkResult res
  723. cdef uint32_t rgba = (rgba_tuple[0] << 24) + (rgba_tuple[1] << 16) + (rgba_tuple[2] << 8) + rgba_tuple[3]
  724. cdef uint8_t charset = 0 # Character set (0 - ANSI, 1 - UTF16, 255 - NeuroMotive ANSI)
  725. cdef bytes py_bytes = comment_string.encode()
  726. cdef const char* comment = py_bytes
  727. res = cbSdkSetComment(<uint32_t> instance, rgba, charset, comment)
  728. def get_sys_config(int instance=0):
  729. cdef cbSdkResult res
  730. cdef uint32_t spklength
  731. cdef uint32_t spkpretrig
  732. cdef uint32_t sysfreq
  733. res = cbSdkGetSysConfig(<uint32_t> instance, &spklength, &spkpretrig, &sysfreq)
  734. handle_result(res)
  735. return {'spklength': spklength, 'spkpretrig': spkpretrig, 'sysfreq': sysfreq}
  736. def set_spike_config(int spklength=48, int spkpretrig=10, int instance=0):
  737. cdef cbSdkResult res
  738. res = cbSdkSetSpikeConfig(<uint32_t> instance, <uint32_t> spklength, <uint32_t> spkpretrig)
  739. handle_result(res)
  740. cdef extern from "numpy/arrayobject.h":
  741. void PyArray_ENABLEFLAGS(np.ndarray arr, int flags)
  742. cdef class SpikeCache:
  743. cdef readonly int inst, chan, n_samples, n_pretrig
  744. cdef cbSPKCACHE *cache
  745. cdef int last_valid
  746. def __cinit__(self, int channel=1, int instance=0):
  747. self.inst = instance
  748. self.chan = channel
  749. cdef cbSPKCACHE ignoreme # Just so self.cache is not NULL... but this won't be used by anything
  750. self.cache = &ignoreme # because cbSdkGetSpkCache changes what self.cache is pointing to.
  751. cdef cbSdkResult res = cbSdkGetSpkCache(self.inst, self.chan, &self.cache)
  752. handle_result(res)
  753. self.last_valid = self.cache.valid
  754. sys_config_dict = get_sys_config(instance)
  755. self.n_samples = sys_config_dict['spklength']
  756. self.n_pretrig = sys_config_dict['spkpretrig']
  757. @cython.boundscheck(False) # turn off bounds-checking for entire function
  758. def get_new_waveforms(self):
  759. cdef int new_valid = self.cache.valid
  760. cdef int new_head = self.cache.head
  761. cdef int n_new = min(new_valid - self.last_valid, 400)
  762. cdef np.ndarray[np.int16_t, ndim=2, mode="c"] np_waveforms = np.empty((n_new, self.n_samples), dtype=np.int16)
  763. cdef np.ndarray[np.uint8_t, ndim=1] np_unit_ids = np.empty(n_new, dtype=np.uint8)
  764. cdef int wf_ix, pkt_ix, samp_ix
  765. for wf_ix in range(n_new):
  766. pkt_ix = (new_head - 2 - n_new + wf_ix) % 400
  767. np_unit_ids[wf_ix] = self.cache.spkpkt[pkt_ix].unit
  768. # Instead of per-sample copy, we could copy the pointer for the whole wave to the buffer of a 1-d np array,
  769. # then use memory view copying from 1-d array into our 2d matrix. But below is pure-C so should be fast too.
  770. for samp_ix in range(self.n_samples):
  771. np_waveforms[wf_ix, samp_ix] = self.cache.spkpkt[pkt_ix].wave[samp_ix]
  772. #unit_ids_out = [<int>unit_ids[wf_ix] for wf_ix in range(n_new)]
  773. PyArray_ENABLEFLAGS(np_waveforms, np.NPY_OWNDATA)
  774. self.last_valid = new_valid
  775. return np_waveforms, np_unit_ids
  776. cdef cbSdkResult handle_result(cbSdkResult res):
  777. if (res == CBSDKRESULT_WARNCLOSED):
  778. print("Library is already closed.")
  779. if (res < 0):
  780. errtext = "No associated error string. See cbsdk.h"
  781. if (res == CBSDKRESULT_ERROFFLINE):
  782. errtext = "Instrument is offline."
  783. elif (res == CBSDKRESULT_CLOSED):
  784. errtext = "Interface is closed; cannot do this operation."
  785. elif (res == CBSDKRESULT_ERRCONFIG):
  786. errtext = "Trying to run an unconfigured method."
  787. elif (res == CBSDKRESULT_NULLPTR):
  788. errtext = "Null pointer."
  789. raise RuntimeError(("%d, " + errtext) % res)
  790. return res