-
Notifications
You must be signed in to change notification settings - Fork 9
Using JISA in Python
JISA packages can be used in Python by installing the pyjisa
package. This makes use of jpype
which "hooks" into the Java Virtual Machine to create and interact with Java objects from inside Python. Therefore, you will also need Java 11 (or newer) installed to uses JISA in Python.
The end result is that by installing the pyjisa
package in Python you will be able to import JISA classes as if they were in Python packages themselves.
- Installing PyJISA
- Importing JISA into Python
- Specifying Java Path
- Note on GUIs
- Callbacks and Lambdas
- CLI Example
- GUI Example
To install the pyjisa
package you can use pip to get it from github like so:
pip install git+https://github.com/OE-FET/PyJISA.git
This will install both pyjisa
and (if you need it) jpype1
. Assuming this worked then you should now be able to import and start pyjisa
like so:
import pyjisa
pyjisa.load()
Calling pyjisa.load()
is important as this is what starts up the Java Virtual Machine and hooks your current Python session into it, enabling you to import JISA classes.
To update the copy of JISA used by PyJISA to its latest verion, you can use updateJISA()
like so:
import pyjisa
pyjisa.updateJISA()
This will redownload JISA.jar from github, even if you already have the latest version. Therefore, it's a bad idea to incorporate this into a JISA program. Just run it manually in a python terminal when you want to update.
To start using JISA packages you need to import pyjisa
and then call load()
from it, then you can import JISA classes as you would any other Python classes:
import pyjisa
pyjisa.load()
from jisa.gui import GUI
GUI.infoAlert("Hello World!")
If the wrong installation of Java is chosen by pyjisa
when you call load()
or it can't find it then you can specify which one to use by supplying the path to load()
like so:
import pyjisa
pyjisa.load("/path/to/java")
For example, on Linux:
import pyjisa
pyjisa.load("/usr/lib/jvm/java-13-openjdk-amd64")
or on Windows:
import pyjisa
pyjisa.load("C:\\Program Files\\AdoptOpenJDK\\jdk-13.0.2.8-hotspot")
For whatever reason, Python terminates when it reaches the end of a script, even if the JISA GUI thread is still running. For this reason, if you were to make something like this:
import pyjisa
pyjisa.load()
from jisa.gui import Plot
plot = Plot("Plot", "X", "Y")
plot.setExitOnClose(True)
plot.show()
Then the script will terminate immediately after plot.show()
has been called resulting in the plot window opening then immediately closing as the script terminating also terminates the JVM.
The normal behaviour you'd expect from this is for the script to remain running until the plot is closed because of plot.setExitOnClose(True)
. To make it behave like this, you will need to make the Python script block after creating and showing the GUI. You can do this by using GUI.waitForExit()
like so:
import pyjisa
pyjisa.load()
from jisa.gui import Plot, GUI
plot = Plot("Plot", "X", "Y")
plot.setExitOnClose(True)
plot.show()
GUI.waitForExit()
This will cause the current Python thread to wait indefinitely. When the plot window is closed, JISA
will call System.exit(0)
(on the JVM) which will cause the waitForExit()
call to be interrupted and return, thus also ending the Python script.
JPype has a bit of a limitation when it comes to passing function-type objects as arguments. To get around this, the pyjisa package provides a number of methods to wrap a Python method reference or lambda so that JISA methods can accept them.
For instance, the various methods to add buttons to GUI elements require a SRunnable
object which you would normally define in Java with a method reference or lambda. To turn a Python method reference or lambda into a SRunnable
you can use the SRunnable(...)
factory in the pyjisa
package. For example, adding a toolbar button to a Grid
element using a method reference:
import pyjisa
pyjisa.load()
from pyjisa import SRunnable
from jisa.gui import Grid, GUI
grid = Grid("Grid Title")
def onClick():
GUI.infoAlert("Button Clicked!")
grid.addToolbarButton("Click Me!", SRunnable(onClick))
or by using a lambda:
import pyjisa
pyjisa.load()
from pyjisa import SRunnable
from jisa.gui import Grid, GUI
grid = Grid("Grid Title")
grid.addToolbarButton("Click Me!", SRunnable(lambda: GUI.infoAlert("Button Clicked!")))
Similarly, there are Predicate(...)
and Evaluable(...)
methods for Predicate
and Evaluable
objects respectively.
import pyjisa
import time
pyjisa.load()
from jisa.devices import K2450
from jisa.addresses import GPIBAddress
from jisa.experiment import ResultList, Col
from jisa.maths import Range
def main():
try:
smu = K2450(GPIBAddress(24))
except:
print("Error connecting to SMU")
quit()
data = ResultList(Col("Voltage", "V"), Col("Current", "A"))
smu.setVoltage(0.0)
smu.turnOn()
for v in Range.linear(0, 5):
print("Measuring at %s V..." % v)
smu.setVoltage(v)
time.sleep(0.5)
data.addData(smu.getVoltage(), smu.getCurrent())
smu.turnOff()
smu.close()
print("Complete, results:")
data.outputTable()
main()
The resulting output would be something like:
Measuring at 0.0 V...
Measuring at 1.0 V...
Measuring at 2.0 V...
Measuring at 3.0 V...
Measuring at 4.0 V...
Measuring at 5.0 V...
Complete, results:
+==============+==============+
| Voltage [V] | Current [A] |
+==============+==============+
| 0.000000 | 0.000000 |
+--------------+--------------+
| 1.000000 | 0.004792 |
+--------------+--------------+
| 2.000000 | 0.009183 |
+--------------+--------------+
| 3.000000 | 0.013541 |
+--------------+--------------+
| 4.000000 | 0.018464 |
+--------------+--------------+
| 5.000000 | 0.022093 |
+--------------+--------------+
import pyjisa
import time
pyjisa.load()
from pyjisa import SRunnable
from jisa.gui import Grid, Plot, Table, GUI
from jisa.devices import K2450
from jisa.addresses import GPIBAddress
from jisa.experiment import ResultList, Col
from jisa.maths import Range
def main():
try:
smu = K2450(GPIBAddress(24))
except:
GUI.errorAlert("Error connecting to Keithley 2450")
return
data = ResultList(Col("Voltage", "V"), Col("Current", "A"))
plot = Plot("Plot of Results")
series = plot.createSeries().watch(data, 0, 1)
table = Table("Table of Results", data)
grid = Grid("Voltage Sweep", plot, table)
def runMeasurement():
data.clear()
series.clear()
smu.turnOff()
smu.setVoltage(0.0)
smu.turnOn()
for v in Range.linear(0, 5):
smu.setVoltage(v)
time.sleep(0.5)
data.addData(smu.getVoltage(), smu.getCurrent())
smu.turnOff()
def save():
file = GUI.saveFileSelect()
if file is not None:
data.output(file)
grid.addToolbarButton("Start Measurement", SRunnable(runMeasurement))
grid.addToolbarButton("Save Data", SRunnable(save))
grid.setExitOnClose(True)
grid.show()
GUI.waitForExit()
main()
Resuling in the following when the "Start Measurement" button is pressed:
- Getting Started
- Object Orientation
- Choosing a Language
- Using JISA in Java
- Using JISA in Python
- Using JISA in Kotlin
- Exceptions
- Functions as Objects
- Instrument Basics
- SMUs
- Thermometers (and old TCs)
- PID and Temperature Controllers
- Lock-Ins
- Power Supplies
- Pre-Amplifiers
- Writing New Drivers