swharden/pyABF

add support for user list stimulus parameters

KBenthall opened this issue ยท 20 comments

I'm trying to plot my stimulus file for a protocol in which I used the 'list' function in Clampex to define current steps, and using sweepC for this plots incorrect step amplitudes. It seems that pyabf is not able to determine the true current step amplitude when 'list' is used in the protocol editor.

t-b commented

@KBenthall Can you attach an example file?

Yes, thank you, would it be possible for me to send via email instead of posting here?

Yes, thank you, would it be possible for me to send via email instead of posting here?

It would really help if you could submit a sample ABF publicly (here) so we can all work on it ๐Ÿ‘

Sorry, I'm trying to upload but the .abf and .atf files aren't supported in the comment section. Is there another place I'm supposed to upload instead?

Hi @KBenthall, thanks for the additional information! Unfortunately it looks like you sen that email to pyABF@noreply.github.com so the ABF never made it.

If you e-mail the ABF to my personal email address swharden@gmail.com I can upload the ABF here and @t-b and I can take a closer look

For those of us less familiar with what a "user list" is, this is what the manual says:

The User List, on each of the Stimulus tabs, provides a way of customizing one of a range of
analog and digital output features, overriding the generalized settings made elsewhere in the
Edit Protocol dialog. The selected parameter can be set to arbitrary values for each sweep in
a run by entering the desired values in the List of parameter values field. So, for example, rather than having sweep start-to-start times remain constant for every sweep in a run, specific sweep start-to-start times can be set for each sweep. Alternatively, rather than being forced to increase or decrease the duration of a waveform epoch in regular steps with each successive sweep by setting a duration delta value, you can set independent epoch durations for each sweep. All aspects of the conditioning train, the number of subsweeps in P/N Leak Subtraction, and command waveform amplitudes and durations, besides other output features, can be overridden from the User List.

TLDR: the user list is a set of values which can override one feature of the stimulus waveform by manually defining it as a list of numbers

@KBenthall user lists are definitely not supported by pyABF (yet). I've never seen an ABF file which uses this feature. However, your ABF may help us add support for it.

A sample ABF with a user list stimulus property is now available for download: 2020_03_02_0000.abf (Thanks @KBenthall!)

The current step list is expected to be (in pA): [-200, -150, -100, -50, 0, 25, 50, 100, 150, 200, 250, 300, 350, 400, 500, 600]

I took a closer look at the ABF. For what it's worth, the command current is in the second channel so it could be accessed if desired.

abf = pyabf.ABF("2020_03_02_0000.abf")
for sweepNumber in abf.sweepList:
    plt.subplot(311)
    abf.setSweep(sweepNumber, channel=0)
    plt.plot(abf.sweepX, abf.sweepY, color='b', lw=.5)
    plt.subplot(313)
    plt.plot(abf.sweepX, abf.sweepC, color='r', lw=.5)
    plt.subplot(312)
    abf.setSweep(sweepNumber, channel=1)
    plt.plot(abf.sweepX, abf.sweepY, color='k', lw=.5)

This script produces the following image

image

which is comparable to clampfit

image

sweepC indeed looks wrong when plotted with pyABF, but it also looks wrong when "create stimulus waveform signal" is used in clampfit ๐Ÿ˜ˆ so it isn't quite as egregious of a bug as I initially thought!

I'll leave this ticket open until I get time to take a closer look at the header composition of the file to see if the user list of values can be retrieved anyway (in cases where perhaps they were not recorded in a second channel).

Ah, this is so great, thank you! I will try it out asap.
-- Katie Benthall Bateup Lab

Sounds good! If you hit any snags let me know.

I have the same issue. sweepC plots the wrong DAC values in my voltage clamp data (the values are all zeros which correspond to the stimulus waveform signal).

Unlike, KBenthall's 'List function' protocol, I voltage clamp the cell directly through my amplifier (Multiclamp 700B) not through my stimulus file, thus the values read zero.

I can see the correct DAC values in the header 'data' 2D array.

I can see the correct DAC values in the header 'data' 2D array.

Hi @IndigeNerd, thanks for posting this! Can you send me a sample ABF demonstrating this? It will make it easier for me to develop against. You can email it to me swharden@gmail.com if it's small enough, otherwise something like dropbox is the way to go.

Thanks!

I can see the correct DAC values in the header 'data' 2D array.

I'm a little confused by this. Can you post a screenshot of what this means (and also to show what the correct values for your ABF should be?) Thanks!

I have the same issue. sweepC plots the wrong DAC values in my voltage clamp data (the values are all zeros which correspond to the stimulus waveform signal).

Unlike, KBenthall's 'List function' protocol, I voltage clamp the cell directly through my amplifier (Multiclamp 700B) not through my stimulus file, thus the values read zero.

I can see the correct DAC values in the header 'data' 2D array.

The solution to this issue was similar to the one @KBenthall found. The command trace is in the second channel, so the solution was to do something like

abf.setSweep(sweepNumber=0, channel=1)
print(abf.sweepY)

multi-channel ABF tutorial:
https://swharden.com/pyabf/tutorial.php#multichannel

For what it's worth, here's how to access the values of the user list programmatically. Note that this just returns a short list of the values, not the command waveform.

def GetUserList(abf):
    """
    Return the ABF user list as a list of values, 
    or return None if the ABF has no user list.
    """
    assert isinstance(abf, pyabf.ABF)
    if not hasattr(abf, '_stringsSection'):
        return None
    firstBlock = abf._stringsSection.strings[0]
    firstBlockStrings = firstBlock.split(b'\x00')
    userList = firstBlockStrings[-2].decode("utf-8")
    userList = userList.split(",")
    try:
        return [float(x) for x in userList if x]
    except:
        return None
abfWithUserList = pyabf.ABF(PATH_DATA+"/2020_03_02_0000.abf")
abfWithoutUserList = pyabf.ABF(PATH_DATA+"/171116sh_0020.abf")

print(GetUserList(abfWithUserList))
print(GetUserList(abfWithoutUserList))
[-200.0, -150.0, -100.0, -50.0, 0.0, 25.0, 50.0, 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0, 500.0, 600.0]
None

Interestingly, a few other ABFs in the data folder have a user list:

ABF User List
171117_HFMixFRET.abf [-100.0, 180.0, 160.0, 140.0, 120.0, 100.0, 80.0, 60.0, 40.0, 20.0, 0.0, -20.0, -60.0]
19212027.abf [-50.0, -55.0, -60.0, -65.0, -70.0, -75.0, -80.0, -85.0, -90.0, -95.0, -100.0, -105.0, -110.0, -115.0, -120.0]
2020_03_02_0000.abf [-200.0, -150.0, -100.0, -50.0, 0.0, 25.0, 50.0, 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0, 500.0, 600.0]

I might as well expose this as a class property to make it easier to access

pip install --upgrade pyabf

with pyABF 2.2.4 you can now:

print(abf.userList)

thanks again!