Closed satabios closed 3 years ago
Hi,
thanks for notifying us! Our colleague who did most of the scripting for stimulation is on vacation this week but will get back to you next week. In the meantime, could you attach an example Python script where SetElectrodeMode
crashes? This makes it easier for us to replicate the issue. Which Python version are you using?
Regarding the stimulation code that doesn't produce artifacts: Are you referring to the Python code (Stimulation.py
) or to the Matlab or C# examples?
Best, Armin
My task was to replicate the MATLAB stimulation script provided in the repository into Python. In MATLAB I was trying to stimulate a set of electrodes with a set of train pulses. Here I use "SetElectrodeMode" method to select and enable them for stimulation. I also use "SetBlankingEnable" for blanking the electrodes. This code worked expected by stimulating the corresponding electrodes as soon as "device.SendStart(1)" command was sent.
MATLAB SCRIPT
assembly = NET.addAssembly( 'C:\Users\45c\Downloads\McsUsbNet-5.1.6\McsUsbNet-5.1.6\x64\McsUsbNet.dll');
device = Mcs.Usb.CStg200xDownloadNet();
deviceList = Mcs.Usb.CMcsUsbListNet(Mcs.Usb.DeviceEnumNet.MCS_STG_DEVICE);
fprintf('Found %d STGs\n', deviceList.GetNumberOfDevices());
for i=1:deviceList.GetNumberOfDevices()
SerialNumber = char(deviceList.GetUsbListEntry(i-1).SerialNumber);
fprintf('Serial Number: %s\n', SerialNumber);
end
% Connect to the first STG object
status = device.Connect(deviceList.GetUsbListEntry(0));
if status == 0
% Register cleanup function. This ensures that the device
% disconnects when the run_stim function terminates
cleanupObj = onCleanup(@()cleanup_stim(device));
electrode = uint32(85);
% ElectrodeMode: emManual: electrode is permanently selected for stimulation
device.SetElectrodeMode(electrode, Mcs.Usb.ElectrodeModeEnumNet.emManual);
% ElectrodeDacMux: DAC to use for Stimulation
device.SetElectrodeDacMux(electrode, 0, Mcs.Usb.ElectrodeDacMuxEnumNet.Stg1);
% ElectrodeEnable: enable electrode for stimulation
device.SetElectrodeEnable(electrode, 0, true);
% BlankingEnable: false: do not blank the ADC signal while stimulation is running
device.SetBlankingEnable(electrode, false);
% AmplifierProtectionSwitch: false: Keep ADC connected to electrode even while stimulation is running
device.SetEnableAmplifierProtectionSwitch(electrode, false);
% array of amplitudes and duration
amplitude_array = int32([+200000 -200000]); % Amplitude in uV
duration_array = uint64([1000000 1000000]); % Duration in us
% use voltage stimulation
device.SetVoltageMode();
% send stimulus data to device
device.PrepareAndSendData(uint32(0), NET.convertArray(amplitude_array, 'System.Int32'), NET.convertArray(duration_array, 'System.UInt64'), Mcs.Usb.STG_DestinationEnumNet.channeldata_voltage);
% connect all stimulation channels to the first trigger and repeat the
% pulse 3 times
device.SetupTrigger(uint32(0), NET.convertArray(255, 'System.UInt32'), NET.convertArray(255, 'System.UInt32'), NET.convertArray(1, 'System.UInt32'));
% start the first trigger
device.SendStart(1);`
To replicate the same functionality in the Python. I used the same script provided at https://github.com/multichannelsystems/McsUsbNet_Examples/blob/master/Examples/Python/Stimulation.py to enable the "SetElectrodeMode" at https://github.com/multichannelsystems/McsUsbNet_Examples/blob/master/Examples/Python/Stimulation.py#L40. To my dismay it threw up an error stating that "No method matches given arguments for SetElectrodeMode", even though the object contained the in bound function in its attributes I was unable to access it. Could you suggest me how to use "SetElectrodeMode" in Python? If not how should I go about the same. The python script looked something like below.
PYTHON SCRIPT
`import time
import clr
from System import Action
from System import *
clr.AddReference('C:\\Users\\45c\\Downloads\\McsUsbNet-5.1.6\\McsUsbNet-5.1.6\\x64\\McsUsbNet.dll')
from Mcs.Usb import CMcsUsbListNet
from Mcs.Usb import DeviceEnumNet
from Mcs.Usb import CStg200xDownloadNet
from Mcs.Usb import McsBusTypeEnumNet
from Mcs.Usb import STG_DestinationEnumNet
from Mcs.Usb import ElectrodeModeEnumNet
from Mcs.Usb import ElectrodeDacMuxEnumNet
from Mcs.Usb import *
def PollHandler(status, stgStatusNet, index_list):
print('%x %s' % (status, str(stgStatusNet.TiggerStatus[0])))
deviceList = CMcsUsbListNet(DeviceEnumNet.MCS_DEVICE_USB)
print("found %d devices" % (deviceList.Count))
for i in range(deviceList.Count):
listEntry = deviceList.GetUsbListEntry(i)
print("Device: %s Serial: %s" % (listEntry.DeviceName,listEntry.SerialNumber))
device = CStg200xDownloadNet();
device.Stg200xPollStatusEvent += PollHandler;
device.Connect(deviceList.GetUsbListEntry(0))
voltageRange = device.GetVoltageRangeInMicroVolt(0);
voltageResulution = device.GetVoltageResolutionInMicroVolt(0);
currentRange = device.GetCurrentRangeInNanoAmp(0);
currentResolution = device.GetCurrentResolutionInNanoAmp(0);
print('Voltage Mode: Range: %d mV Resolution: %1.2f mV' % (voltageRange/1000, voltageResulution/1000.0))
print('Current Mode: Range: %d uA Resolution: %1.2f uA' % (currentRange/1000, currentResolution/1000.0))
channelmap = Array[UInt32]([85,0,0,0])
syncoutmap = Array[UInt32]([85,0,0,0])
repeat = Array[UInt32]([10,0,0,0])
amplitude = Array[Int32]([-10000,10000]);
duration = Array[UInt64]([10000,10000]);
electrode = UInt32(85)
device.SetElectrodeMode(electrode, ElectrodeModeEnumNet.emManual);
device.SetElectrodeDacMux(electrode, 0, ElectrodeDacMuxEnumNet.Stg1);
device.SetElectrodeEnable(electrode, 0, True);
device.SetBlankingEnable(electrode, False);
# AmplifierProtectionSwitch: false: Keep ADC connected to electrode even while stimulation is running
device.SetEnableAmplifierProtectionSwitch(electrode, False);
device.SetupTrigger(0, channelmap, syncoutmap, repeat)
device.SetVoltageMode();
device.PrepareAndSendData(0, amplitude, duration, STG_DestinationEnumNet.channeldata_voltage)
device.SendStart(1)
time.sleep(10)
device.Disconnect()`
The python version currently in use is 3.7. Regarding the stimulation code that doesn't produce artifacts: I used the python original Stimulation script provided in the repository https://github.com/multichannelsystems/McsUsbNet_Examples/blob/master/Examples/Python/Stimulation.py
Thank you very much for the detailed explanation! There are two minor issues with the Python code, but they probably weren't present in your original script:
electrode = uint32(101);
should probably be electrode = UInt32(101);
# at the start of your script
from Mcs.Usb import ElectrodeModeEnumNet
# then change your device.SetElectrodeMode line to
device.SetElectrodeMode(electrode, ElectrodeModeEnumNet.emManual);
Beyond that I can't do more checks right now as I don't have a MEA2100 available at the moment... I'll refer you to my colleague Peter who will be back next week.
Thank you for your prompt reply. I have updated the code for your perusal. By using
from Mcs.Usb import ElectrodeModeEnumNet
I'm able to use the function device.SetElectrodeMode(electrode, ElectrodeModeEnumNet.emManual);
.
However, when I try to import from Mcs.Usb import ElectrodeDacMuxEnumNet
for device.SetElectrodeDacMux(electrode, 0, ElectrodeDacMuxEnumNet.Stg1);
it prompts the same error.
Also event after importing from Mcs.Usb import ElectrodeModeEnumNet
I'm unable to run the line device.SetElectrodeEnable(electrode, 0, True);
whereas device.SetElectrodeMode(electrode, ElectrodeModeEnumNet.emManual);
works as mentioned above.
Am I importing it the wrong way? Please guide me regarding the same.
Hi,
these errors are due to the 0
in the SetElectrodeDacMux
and SetElectrodeEnable
. Because you are calling a .NET Dll, it is necessary to use the correct data types for function parameters. Both of these functions expect unsigned integers, so the correct way to call them would be:
device.SetElectrodeDacMux(electrode, UInt32(0), ElectrodeDacMuxEnumNet.Stg1)
device.SetElectrodeEnable(electrode, UInt32(0), True)
The same is true for other functions in your script, e.g. SetupTrigger
, PrepareAndSendData
and SendStart
. Please check the documentation (https://github.com/multichannelsystems/McsUsbNet/blob/master/docu/McsUsbNet.pdf) for the correct data types for the respective functions.
Thanks that resolved the issues. Now the script is able to stimulate the electrode. One more quick question how to stimulate multiple electrodes at the same time? I extrapolated the above example and have attached the code below, Can you verify if I have implemented it the correct way?
import time
import clr
from System import Action
from System import *
clr.AddReference('C:\\Users\\45c\\Downloads\\McsUsbNet-5.1.6\\McsUsbNet-5.1.6\\x64\\McsUsbNet.dll')
from Mcs.Usb import CMcsUsbListNet
from Mcs.Usb import DeviceEnumNet
from Mcs.Usb import CStg200xDownloadNet
from Mcs.Usb import McsBusTypeEnumNet
from Mcs.Usb import STG_DestinationEnumNet
from Mcs.Usb import ElectrodeModeEnumNet
from Mcs.Usb import ElectrodeDacMuxEnumNet
def PollHandler(status, stgStatusNet, index_list):
print('%x %s' % (status, str(stgStatusNet.TiggerStatus[0])))
deviceList = CMcsUsbListNet(DeviceEnumNet.MCS_DEVICE_USB)
print("found %d devices" % (deviceList.Count))
for i in range(deviceList.Count):
listEntry = deviceList.GetUsbListEntry(i)
print("Device: %s Serial: %s" % (listEntry.DeviceName,listEntry.SerialNumber))
device = CStg200xDownloadNet();
device.Stg200xPollStatusEvent += PollHandler;
device.Connect(deviceList.GetUsbListEntry(0))
voltageRange = device.GetVoltageRangeInMicroVolt(0);
voltageResulution = device.GetVoltageResolutionInMicroVolt(0);
currentRange = device.GetCurrentRangeInNanoAmp(0);
currentResolution = device.GetCurrentResolutionInNanoAmp(0);
print('Voltage Mode: Range: %d mV Resolution: %1.2f mV' % (voltageRange/1000, voltageResulution/1000.0))
print('Current Mode: Range: %d uA Resolution: %1.2f uA' % (currentRange/1000, currentResolution/1000.0))
channelmap = Array[UInt32]([1,0,0,0])
syncoutmap = Array[UInt32]([1,0,0,0])
repeat = Array[UInt32]([10,0,0,0])
amplitude = [Array[Int32]([-10000,10000]),Array[Int32]([-10000,10000]),Array[Int32]([-10000,10000])]
duration = [Array[UInt64]([10000,10000]),Array[UInt64]([10000,10000]),Array[UInt64]([10000,10000])]
electrode = [3,1,52] #Some list of electrodes I desire to stimulate
for elec in range(len(electrode)):
device.SetElectrodeMode(UInt32(electrode[elec]), ElectrodeModeEnumNet.emManual)
device.SetElectrodeDacMux(UInt32(electrode[elec]), UInt32(0), ElectrodeDacMuxEnumNet.Stg1)
device.SetElectrodeEnable(UInt32(electrode[elec]), UInt32(0), True);
device.SetBlankingEnable(UInt32(electrode[elec]), False);
# AmplifierProtectionSwitch: false: Keep ADC connected to electrode even while stimulation is running
device.SetEnableAmplifierProtectionSwitch(UInt32(electrode[elec]), False);
device.PrepareAndSendData(0, amplitude[elec], duration[elec], STG_DestinationEnumNet.channeldata_voltage)
device.SetupTrigger(0, channelmap, syncoutmap, repeat)
device.SetVoltageMode();
device.SendStart(1)
time.sleep(10)
device.Disconnect()
Glad to hear that it worked! I don't have a way to test your script with hardware at the moment, so I can't run a full test. The only issue with the code I can see is that you are calling PrepareAndSendData
inside the loop. This is not necessary: sending the stimulation data to the device just once is enough. You can move the PrepareAndSendData
call outside of the loop, directly before the SetupTrigger
call.
Since I was trying to stimulate multiple electrodes I was sending each array of stimulation corresponding to that particular array, that is the reason I put PrepareAndSendData
under the loop. Holistically am I stimulating the electrodes in the correct fashion?
Ah, sorry, I overlooked the parameters for the PrepareAndSendData
call: Essentially, what you have configured is that all of your electrodes will receive stimulation from the same stimulus generator (Stg1
, due to the device.SetElectrodeDacMux(UInt32(electrode[elec]), UInt32(0), ElectrodeDacMuxEnumNet.Stg1)
call). Typically, you can only have a single stimulation pattern per stimulus generator, so all your electrodes will receive the same stimulation pattern. Because of this, having
amplitude = Array[Int32]([-10000,10000])
duration = Array[UInt64]([10000,10000])
and then calling device.PrepareAndSendData(0, amplitude, duration, STG_DestinationEnumNet.channeldata_voltage)
sends this pattern to all 3 electrodes.
Just to clarify, even if I change the amplitude
with varying patterns, the stimulus generator would only pick one type of stimulus?
(i.e..) say if I set amplitude = [Array[Int32]([-10000,0,10000]),Array[Int32]([-300,300]),Array[Int32]([-700,700])]
with the corresponding duration intervals. Even then the stimulus would only generate the last pattern?
I have to say that I can't guarantee that the following is correct and it might be better to wait for Peter's answer next week who is the expert on interacting with the stimulator. If you want to use different stimulation patterns on different electrodes, to the best of my knowledge it works like this:
The MEA2100 has 3 independent stimulus generators (Stg1
, Stg2
, Stg3
) and you can configure stimulation patterns (amplitude arrays and duration arrays) for each of them. You can then assign all electrodes that should get the first pattern to Stg1
via the SetElectrodeDacMux
call, electrodes that should get the second pattern to Stg2
, and so on. Then, you can call
PrepareAndSendData(UInt32(0), amplitude[0], duration[0], STG_DestinationEnumNet.channeldata_voltage) # pattern for Stg1
PrepareAndSendData(UInt32(1), amplitude[1], duration[1], STG_DestinationEnumNet.channeldata_voltage) # pattern for Stg2
PrepareAndSendData(UInt32(2), amplitude[2], duration[2], STG_DestinationEnumNet.channeldata_voltage) # pattern for Stg3
If you call PrepareAndSendData
multiple times for the same channel (the first parameter), the stimulation pattern defined for this channel gets overwritten.
Then, you also need make sure that all Stg
s get started, via the SetupTrigger
call. Currently, you've defined it that only Stg1
gets started and the pattern is repeated 10 times. In order to start all Stg
s, you'll need to:
Edit: Removed, because code had errors
Sorry, the last code block had some errors, here is the corrected version:
To start all Stg
s, you can either assign all of them to the same trigger so they are started together:
channelmap = Array[UInt32]([7,0,0,0])
syncoutmap = Array[UInt32]([7,0,0,0])
repeat = Array[UInt32]([10,0,0,0])
device.SetupTrigger(0, channelmap, syncoutmap, repeat)
device.SendStart(1)
The 7
in channelmap
and syncoutmap
has to be interpreted as binary 111
, meaning all 3 Stg
s are assigned to the first trigger, represented as the first entry in the channelmap
, syncoutmap
and repeat
arrays. Then, device.SendStart(1)
starts the first trigger.
Another option would be to assign each Stg
to its own trigger, so you could start them independently from each other:
channelmap = Array[UInt32]([1,2,4,0])
syncoutmap = Array[UInt32]([1,2,4,0])
repeat = Array[UInt32]([10,10,10,0])
device.SetupTrigger(0, channelmap, syncoutmap, repeat)
device.SendStart(7)
Here, 1
, 2
and 4
need to be interpreted as binary 001
, 010
and 100
, meaning Stg1
, Stg2
and Stg3
are assigned to their own triggers. Each trigger is configured with 10 repeats, and device.SendStart(7)
means that all 3 triggers are started at the same time.
I'll close this ticket for now, please reopen it if you experience further issues
I'm using a MEA2100 to conduct my experiments. I have been trying to replicate the MATLAB example of stimulation on the python script. However when I try to access "SetElectrodeMode" inbound method the script crashes. Is there a possible resolution to this? If not is there an exact replication of the MATLAB code on python? One more thing to add, when I run the record example from the repository it works as desired. But when I run the original stimulation code shared from this repository I'm unable to see any artifacts or any sort of distinction that the electrodes have been stimulated.