multichannelsystems / McsUsbNet_Examples

C#, Python and Matlab code examples to interact with the McsUsbNet.dll
BSD 2-Clause "Simplified" License
7 stars 2 forks source link

Python does not support SetElectrodeMode #1

Closed satabios closed 3 years ago

satabios commented 3 years ago

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.

armwal commented 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

satabios commented 3 years ago

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

armwal commented 3 years ago

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:

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.

satabios commented 3 years ago

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.

armwal commented 3 years ago

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.

satabios commented 3 years ago

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()
armwal commented 3 years ago

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.

satabios commented 3 years ago

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?

armwal commented 3 years ago

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.

satabios commented 3 years ago

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?

armwal commented 3 years ago

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 Stgs 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 Stgs, you'll need to:

Edit: Removed, because code had errors

armwal commented 3 years ago

Sorry, the last code block had some errors, here is the corrected version:

To start all Stgs, 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 Stgs 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