bicarlsen / easy-biologic

Python library for communicating with Biologic devices.
GNU General Public License v3.0
18 stars 11 forks source link

Help creating a custom program #4

Closed KF243 closed 2 years ago

KF243 commented 2 years ago

try1.zip

On the vanilla Python environment, my example.py results in showing this error message. Please suggest how this problem is fixed.

bicarlsen commented 2 years ago

Are you running Python v 3.6 or below? If so can you upgrade to v 3.7 or above and try running again.

KF243 commented 2 years ago

Python is upgraded to version 3.9.6, and then the result changes to the following.

min() arg is an empty sequence

try2.zip

bicarlsen commented 2 years ago

This appears to be happening because data from the JV scan to determine the inital Vmpp can not be read. Can you verify that when you run the program a voc.csv and jv.csv file are created before the MPP starts. Is there data in those files?

If you need to get the experiment up and running you can skip the automated Voc and JV scans by using the MPP_Tracking procedure, and supplying the initial Vmpp.

KF243 commented 2 years ago

voc.csv and jv.csv files are created at "C:\Users\kf\data\" when the last error message was shown as follows,

File "C:\Users\kf\AppData\Local\Programs\Python\Python39\lib\site-packages\easy_biologic\base_programs.py", line 1700, in <dictcomp>
    ch: min( data, key = lambda d: d.power ) # power of interest is negative
ValueError: min() arg is an empty sequence

Is the "data" folder created at the correct path?

bicarlsen commented 2 years ago

Yes, that is the correct path. Is there data written into the jv.csv file?

KF243 commented 2 years ago

I re-installed easy-biologic on the anaconda python and could create jv.csv voc.csv and mpp.csv files in data folder at the top directory. I also confirmed the contents in these files. So I think the problem might be caused by the wrong installing of easy-biologic in my python PC. At the next step, I will code to show the results as the graphs with matplotlib and change the details of method.

KF243 commented 2 years ago

I can only find brief comments for methods in your Project description. Could you open the more detail explanation for them?

bicarlsen commented 2 years ago

I re-installed easy-biologic on the anaconda python and could create jv.csv voc.csv and mpp.csv files in data folder at the top directory. I also confirmed the contents in these files. So I think the problem might be caused by the wrong installing of easy-biologic in my python PC. At the next step, I will code to show the results as the graphs with matplotlib and change the details of method.

Great to hear :)

bicarlsen commented 2 years ago

I can only find brief comments for methods in your Project description. Could you open the more detail explanation for them?

Is there a more specific question you have that I can help you with? I am happy to include more examples in the documentation, but that will take me some time.

KF243 commented 2 years ago

MPP may serve us Vmpp by OCV and JV scan if the cell wires are connected with no cell. However, an ocv.csv file with correct data, a jv.csv file without data and no mpp.csv file are created with the following messages, when the cell wires were connected with DC3 dummy cell (in standard 3 electrode connection and this connection was successfully operated with EC-Lab).

(biologicSP300) C:\Users\kfush>python C:\Users\kfush\data\try.py
Traceback (most recent call last):
  File "C:\Users\kfush\data\try.py", line 19, in <module>
    mpp.run( 'C:/Users/kfush/data' )
  File "C:\Users\kfush\anaconda3\envs\biologicSP300\lib\site-packages\easy_biologic\base_programs.py", line 1580, in run
    self.v_mpp = self._run_jv( self.voc, jv_loc, by_channel = by_channel, jv_params = jv_params ) # jv
  File "C:\Users\kfush\anaconda3\envs\biologicSP300\lib\site-packages\easy_biologic\base_programs.py", line 1699, in _run_jv
    mpp = {
  File "C:\Users\kfush\anaconda3\envs\biologicSP300\lib\site-packages\easy_biologic\base_programs.py", line 1700, in <dictcomp>
    ch: min( data, key = lambda d: d.power ) # power of interest is negative
ValueError: min() arg is an empty sequence    

Which kind of problem is causing in my JV scan?

bicarlsen commented 2 years ago

The issue is that for some reason the JV_Scan program that is built into the MPP program isn't collecting any data. Can you please try running a JV_Scan program on it's own, and see if that works.

KF243 commented 2 years ago

The problem depends on the cell and not on the program. Isn't it caused by using Ultra Low Current booster (https://www.biologic.net/products/ultra-low-current-down-to-aa-resolution/)?

bicarlsen commented 2 years ago

Ah, I understand. What happens if you run a CV scan from EC Lab directly?

KF243 commented 2 years ago

There are two boards named 1ch and 2ch in EC Lab (0 and 1 in example.py), which are connected via each booster with a dummy cell or no cell. In EC lab, 1ch showed an Ohmic slope during CV but 2ch gave current over load and failed CV. In the same connection, try30.py (line 15: channels = [0] ) created jv.csv (very small current and no ohmic slope) for 1ch and try31.py (line 16: channels = [1] ) created jv.csv (no data) for 2ch try3.zip .

bicarlsen commented 2 years ago

For channel 2 the failure is expected. The current overload will prevent the measurement from proceeding and the JV_Scan should fail by design. If the technique won't run using EC Lab it appears the issue is with your dummy cell, and not the easy_biologic program.

KF243 commented 2 years ago

Sorry. It was not "current overload" but "Control amplifier overloaded on channel 2" using EC-Lab. It is because of no dummy cell. Using easy-biologic, no dummy cell connection gathered very small current and created jv.cvs file. The problem is occurred when try30.py is used for the connection with a dummy cell. In all cases, Ultra Low Current booster is used because our SP-300 has no general cables.

KF243 commented 2 years ago

Alternately, I am trying the using JV_Scan directly as cbj.py. It seems to run. But I do not know how the data is correctly measured. How the data can be saved in csv file ?

try4.zip

bicarlsen commented 2 years ago

Ah, I understand. I am unsure as to why the measurement is failing. My suggestion would be to just run an MPP_Tracking program then. That way you can bypass teh JV_Scan which is giving you problems. Is that a viable solution for you?

bicarlsen commented 2 years ago

You can save data using the BiologicProgram#save_data method. e.g. jv.save_data( 'path/to/file.csv', by_channel = False ).

KF243 commented 2 years ago

Many thanks. Now, jv_scan data file is successfully created independent of cell connection.

For the next ca measurement, in which ch0 and ch1 are independently polarized at different voltages, eg. 0.1 and 0.2 V, respectively, how do I code it? Using the attached py, both chs seems to be polarized at 0.1 V. try5.zip .

bicarlsen commented 2 years ago

To specify different parameters for each channel you can use a dictionary keyed by channel with values of the specific parameters. e.g.

params = {
  1: { 'start': 0, 'end': 1, 'step': 0.01 },
  2: { 'start': -1, 'end': 0, 'step': 0.05 }  
}
KF243 commented 2 years ago

According your example, I modified for JV_Scan as follows but it does not work.

jv = blp.JV_Scan(device = 
    bl,
    params = { 
        '1': { 'start': 0, 'end': 1, 'step': 0.01 },
        '2': { 'start': -1, 'end': 0, 'step': 0.05 } 
        },
    channels = [0, 1]
    )

Does not need the dictionary 'quote' for a key? For two chs (0 and 1), how does the params relate to channels? Is the params of CA {'voltages', 'durations', etc} instead of { 'start', 'end', 'step', etc} ?

bicarlsen commented 2 years ago

When specifiying channel specific parameters you leave out the channels argument. e.g.

jv = blp.JV_Scan(
    device = bl,
    params = { 
        1: { 'start': 0, 'end': 1, 'step': 0.01 },
        2: { 'start': -1, 'end': 0, 'step': 0.05 } 
        }
    )

ca = blp.CA(
  device = bl,
  params = {
    1: { 'time_interval': 0.01, 'voltages': [ 0.1, 0.2, 0.3 ], durations: [ 1, 1, 1 ] },
    2: { 'time_interval': 0.1, 'voltages': [ 0.4, 0.5, 0.6 ], durations: [ 10, 5, 10 ] }
  }
)
KF243 commented 2 years ago

Thanks. Now both ch0 and ch1 are polarized potentiostatically using the zipped cbj3.py.

For two channel connection, we need to use floating mode instead of ground mode. How can I switch the mode? During the CA running, how can I display measured values such as current?

bicarlsen commented 2 years ago

Glad to hear we're making progress :)

I don't currently have the ability to change the ground mode programmed. I would be happy to implement it, but it may be a week or two until I get around to it. I also don't have an SP300, so won't be able to test it on my own hardware before hand. I suggest creating this as a seperate issue if you would like me to implement it.

To read the current measurement data you can register a callback function using the BiologicProgram#on_data method. Because the program writes its data to file as soon as it is processed, you can also create a separate program that just reads the file. If you will be collecting data over a long period of time, I would also suggest setting the BiologicProgram.data_window attrribute to release memory.

e.g. Using #on_data

def my_logging_function( segment, program ):
  data = segment.data
  print( data )

ca = CA( ... )
ca.on_data( my_logging_function )
KF243 commented 2 years ago

Thanks every time. Ground/floating mode switching was separated as other issue.

cbj32.py using ca = blp.CA(...) does not show the current data. But ca = CA(...) seems to be not accepted on Spyder that shows "CA is undefined name" during the coding. What should be fixed?

try7.zip

bicarlsen commented 2 years ago

ca = CA( ... ) throws an error because you have not imported the class directly, e.g. from easy_biologic.base_programs import CA. Instead you have only import the base_programs module with import easy_biologic.base_programs as blp, so must always access the classes with in with the namespace, i.e. ca = blp.CA( ... ).

In the provided file you have registered the callback function incorrectly., and it doesn't appear you've started the program any where. I also noticed you've provided the bin_file and xlx_file parameters to bl.connect() which you shouldn't have to do.

import easy_biologic as ebl 
import easy_biologic.base_programs as blp

bl = ebl.BiologicDevice( ... )
bl.connect()

def my_logging_function( segment, program ):
  data = segment.data
  print( data )

ca = blp.CA( ... )
ca.on_data( my_logging_function )
ca.run()
KF243 commented 2 years ago

OK. cbj4.py gives us data, the number of which is a inverse of 'time_interval', with a 1-second interval for 'duration'. 1) How can I change the interval of 1 s? 2) During this interval, how can I run an other program (eg. printing some messages depending on measured data)?

try8.zip

bicarlsen commented 2 years ago

I'm a bit confused by your question.

  1. In cbj4.py it appears you have changed the time_interval parameter for channel 0. Are you talking about a different interval?
  2. During the interval I don't have a way to run anything automatically. However you can register as many funcitons as you like with the BiologicProgram#ondata method as shown before. If you would like to run something while waiting you can reimplement the BiologicProgram#_run or BiologicProgram#_retrieve_data methods.
KF243 commented 2 years ago

Sorry for the confusing.

  1. In cbj41.py, I can obtain current of 0 and 1 chs, which is mean value from 10 data, for 5 s, since my_logging_function seems to give the data every 1 s. I want to control this interval (= 1 s).
  2. My purpose is to construct SECM, in which two working electrodes are independently polarized to obtain a current image by scanning one working electrode in X and Y axes with fixed intervals for these axes. I have already coded for the electrode scanning and I want to take current data at each scanning for imaging. Which is the best method for this task, BiologicProgram#ondata, BiologicProgram#_run or BiologicProgram#_retrieve_data?
  3. If CA does not allow the interval for anything, can I continue the polarization of CA even after showing current data?

try9.zip

bicarlsen commented 2 years ago

Okay, I understand a bit better now. I'm not familiar with SECM, but form what I understand you position the electrode spatially, then run a scan, then move the elctrode to a new position and run another scan, continuing the process for each position. If this is the case it seems the best approach would be something like:

  1. Move the electrodes into position.
  2. Run a scan. Save data using the position coordinates.
  3. Repeat 1 and 2 for each position. With this strategy you don't need to use a callback from Biologic. e.g.
    
    # NOTE: This is pseudo-code
    import numpy as np
    from easy_biologic.base_programs import CA

create list of grid coordinate points

x = [ 1, 2, 3 ] y = [ 1, 2, 3 ] xx, yy = np.meshgrid( x, y ) coordinates = np.c_[ xx.ravel(), yy.ravel() ]

electrode = MyElectrodeController() # for example purposes

create Biologic program

ca = CA( ... )

for coord in coordinates:

move electrode to coordinate

electrode.move_to( coord )

initiate scan

datapath = f'x{coord[ 0 ]}-y_{coord[1]}' ca.run( data_path )

KF243 commented 2 years ago

I will check tomorrow morning whether the "for coord in coordinates:" disturbs the potentiostatic polarization or not. With the latest code, I will also check if I can get the data for a short period smaller than 1 s (eg. 100 ms) after each electrode scan.

KF243 commented 2 years ago

cbj5.py results in showing the intermittent polarization like pulse polarization. As you know, just after the polarization a double layer charging takes place and then Faradaic reaction proceeds. Because of the initial charging, several hundreds milli-seconds are needed and the detection of Faradaic reaction is disturbed. For the fine imaging, once started, the polarization does not stop. Can we freeze the polarization status with some functions?

try10.zip

bicarlsen commented 2 years ago

This is possible, although it does start to get a bit complicated. I've attached here an example of a program we use where we perform MPP tracking in combination with a light source. We keep the light on for a specified amount of time and perform MPP tracking, when the light turns off we hold each channel at a different voltage for a given amount of time, then we turn the light source back on and resume MPP tracking.

This should act as a good template for your customization. You will need to subclass either BiologicProgram or CA to add in this functionality, as we have done in the MPP_Tracking_w_Lamp in the attached file. Note that I have removed many details from this file to make it more readable, so if you try to run it there will be errors.

control_example.zip

KF243 commented 2 years ago

Certainly, your template seems to be useful for my target. But it makes errors which is difficult for me to understand. For example, I have no idea what Lamp() provides at line 219 because of no information of bcu and dp. What are defined at the dictionary (**jv_params) at line 245? Do you have more simple examples for MPP_Tracking?

try11.zip

bicarlsen commented 2 years ago

As I mentioned, I removed much of the code to make it a bit more readable, so errors were going to occur. Unfortunately without a better understanding of the specifics of your project it is hard for me to give much input.

The main idea though, is that you can subclass either the BiologicProgram or CA classes and add your control code in the _run method. I have included the full example code below for your reference.

control_example-full.zip

KF243 commented 2 years ago

Please find the attachment which is the specifics of my project. During the XY stepping motions, current data (now, it is dummy) are collected to image. Initially potentiostatic polarization for 0ch and 1ch needs to be started. Current data of either ch at each XY step needs to be measured to image without disturbing polarization.

try12.zip

KF243 commented 2 years ago

Using "segment" provides instantaneous data. Is it possible for my target to collect current data in a csv file and to combine with scanning program? Nevertheless, how to control the providing number of instantaneous data is unclear. In the attached case, time=interval:0.1, durations:10 for 2ch writes a couple of lines 10 times. How do I increase the line number in the same durations.

try13.zip

bicarlsen commented 2 years ago

I'm not sure I completely understand your questions, but I will do my best to answer them.

Yes, you can write the data out to a file as you wish. I suggest looking at the BiologicProgram.save_data method for an example. Be aware that in the DataSegment there is both processd and unprocessed data. For the data to make sense you must access the .data attribute of the segement.

You can not control the amount of data coming from the BioLogic device. Essentially it will take a measurement whenever it surpasses one of the thresholds set up in the program parameters. This data is stored in a buffer. When Biologic._retrieve_data is called it pulls all the data out of this buffer.

Attached is some pseudocode that may be useful for you. I have not tested it, and there are likely errors, but hopefully it gives you an idea of ho I would approach the program from what I understand of it. secm.zip

KF243 commented 2 years ago

I am overwhelmingly lacking in reference information. How I can find BiologicProgram.save_data and Biologic._retrieve_data ?

Thanks. Compared with mpp_lamp_track.py, certainly, secm.py may be useful for me. But it is still difficult for me to understand. Only for SECM( blp.CA) operation (without PositionController() operation), how are voltages and params set? secm1.py results in the followings.

C:\Users\kfush>python C:\Users\kfush\data\secm1.py Traceback (most recent call last): File "C:\Users\kfush\data\secm1.py", line 184, in secm.run( 'data' ) File "C:\Users\kfush\AppData\Local\Programs\Python\Python39\lib\site-packages\easy_biologic\base_programs.py", line 545, in run steps = len( ch_params[ 'voltages' ] ) KeyError: 'voltages'

try14.zip

bicarlsen commented 2 years ago

The documentation for both BiologicProgram.save_data and Biologic._retrieve_data is included in their docstrings which you can see in the program file.

You can update a program's parameters using the BiologicDevice.update_parameters method. An example of how to use this is seen in the CA.update_voltages method.

The reason your error is occurring is becasue of the way params are being passed in. If params is a dictionary keyed by the channels, you can not pass the channels parameter as well. Only use the channels parameter if all the channels use the same parameters, in which case the parameters should be a dictionary of the parameters only, not including the channels. e.g.

# all channels with same parameters
secm = SECM(  
    bl,
    coordinates,
    position_controller,
    params = { 'time_interval': 0.1, 'voltages': [ 0.1 ], 'durations': [ 10 ] },
    channels = [ 0, 1 ]
)

# or
# parameters specified by channel
secm = SECM(  
    bl,
    coordinates,
    position_controller,
    params = {
        0: { 'time_interval': 0.1, 'voltages': [ 0.1 ], 'durations': [ 10 ] },
        1: { 'time_interval': 0.1, 'voltages': [ 0.2 ], 'durations': [ 10 ] }
    }
)
KF243 commented 2 years ago

line184: what is the role of argument 'data' for secm.run()? SECM12.py can run because voltage of -0.2V for 10s at 1ch is detected actually but go somewhere without error message. How do I check the status of the program? How do I show the measured current?

try15.zip

bicarlsen commented 2 years ago

'data' was put here accidentally. It should be removed.

You can retrieve the measured current either from the BiologicProgram.data attritute, or you could read it in from the file it is being saved to.

What sort of status do you want from the program?

KF243 commented 2 years ago

Line 184 in secm12.py was just corrected. But it still show nothing and does not stop. I want to know the "status" or situation, in which the program is running. Specially, I want to know which line is running or which line is wrong.

To retrieve the measured current using BiologicProgram.data attribute or reading from the file, do I need to define a new "class" like PositionController or SECM?

bicarlsen commented 2 years ago

To track which line is running you should use an IDE that allows you to insert break points. I believe VSCode, PyCharm, and DataSpell allow this functionality, and there may be others. You can also place print or logging statements in the function to output what part of the program is currently running.

You don't need to create a new class to retrieve the data. If you are within the program you can use self.data, or from outside you can do something like secm = SECM(); data = secm.data.

KF243 commented 2 years ago

It took a little time to develop VSCode environment in the PC. VSCode shows some errors for secm.py as follows.

Exception has occurred: AttributeError 'SECM' object has no attribute 'channel' File "C:\Users\kfush\data\secm13.py", line 156, in _run self.update_voltages( voltages, channels ) File "C:\Users\kfush\data\secm13.py", line 186, in secm.run()

How do I modify to attribute 'channel' for update_voltages? Is it OK secm.run() is in line 186?

try16.zip

bicarlsen commented 2 years ago

Ah, apologies for this. This seems to be an issue with the CA program, try using the CALimit program instead. i.e.

class SECM( blp.CALimit ):
  ...

Or you can download and install commit 118bf5, which chould resolve this issue.

KF243 commented 2 years ago

Line 55 was modified to 'class SECM( blp.CALimit ):', but the error message does not change.

Exception has occurred: TypeError
an integer is required (got type NoneType)
  File "C:\Users\kfush\data\secm13.py", line 156, in _run
    self.update_voltages( voltages, channels )
  File "C:\Users\kfush\data\secm13.py", line 186, in <module>
    secm.run()

try16b.zip

bicarlsen commented 2 years ago

In fact, this is a different error. In your code the line occurs on the line self.update_voltages( voltages, channels ). First, channels should have probably been self.channels. But more importantly update_voltages() does not take channels as a parameter. Please be sure to look at the documentation of the function for its correct use.

KF243 commented 2 years ago

Modified secm13.py shows the following error messages. Exception has occurred: TypeError float() argument must be a string or a number, not 'dict' File "C:\Users\kfush\data\secm13.py", line 158, in _run self.update_voltages( voltages ) File "C:\Users\kfush\data\secm13.py", line 188, in secm.run()

The documentation may be from line 560 in base_programs.py. But which is the documentation for self.channels?

try16c.zip