During the I2K workshop on “Exploring large volumetric data with Fiji Python scripting” in December 1st, 2020, I typed in some scripts which I am sharing here. At the bottom, I include pointers to resources we had used as well, and links to documentation.
These python (jython) scripts were run in Fiji’s Script Editor, using the “persistent” checkbox so as to keep the same script interpreter instance running and therefore being able to access variables, functions and classes after having executed the script.
- How to listen to image events with an ij.ImageListener:
from ij import ImagePlus from ij import ImageListener class FileOpenListener(ImageListener): def imageClosed(self, imp): pass def imageOpened(self, imp): print imp def imageUpdated(self, imp): pass listener = FileOpenListener() ImagePlus.addImageListener(listener) # Uncomment and execute by selecting it and then control+shift+R # to stop the listener from responding #ImagePlus.removeImageListener(listener)
- Accessing class fields via reflection:
Here we had the issue of, having lost a handle to the ij.ImageListener, we had to grab it from a
static private field of ImagePlus, and we did so by exploring the fields of the class and grabbing the correct one, the Vector
listeners, via reflection:
(Note: @Wayne it would be lovely to add a method
from ij import ImagePlus # accessing a static class field field = ImagePlus.getDeclaredField("listeners") field.setAccessible(True) listeners = field.get(None) print listeners ImagePlus.removeImageListener(listeners) # To discover a field named similar to "listener" for field in ImagePlus.getDeclaredFields(): if field.getName().lower().find("listener") > -1: print field
- A GUI that starts with an empty JPanel, then adds two JButton, then steals the ImageCanvas from an open image (you’ll need one to run this script) and adds it, and then we decide to organize and position the UI elements by using a GridBagLayout with specific GridBagConstraints for every element.
The script was run in pieces, interactively, with the “persistent” checkbox activated, and then selecting blocks of code and running them with shift+control+R.
from javax.swing import JPanel from javax.swing import JFrame from java.awt import Dimension from javax.swing import JButton from java.awt.event import ActionListener from ij import IJ from java.awt import GridBagLayout from java.awt import GridBagConstraints # Swing library frame = JFrame("first window") panel = JPanel() panel.setPreferredSize(Dimension(512, 512)) frame.getContentPane().add(panel) frame.pack() frame.setVisible(True) button = JButton("Open an image") panel.add(button) class Action(ActionListener): def actionPerformed(self, event): print "Button was clicked!" IJ.open() action = Action() button.addActionListener(action) frame.pack() def actionButton2(event): print "OK!" # Easier, more direct way of defining an ActionListener: by keyword argument button2 = JButton("OK", actionPerformed=actionButton2) panel.add(button2) frame.pack() # Let's steal a canvas from an open image imp = IJ.getImage() window = imp.getWindow() canvas = window.getCanvas() panel.add(canvas) frame.pack() gridbag = GridBagLayout() c = GridBagConstraints() panel.setLayout(gridbag) # Canvas: at top left c.gridx = 0 c.gridy = 0 c.gridheight = 2 c.fill = GridBagConstraints.NONE c.anchor = GridBagConstraints.NORTHWEST gridbag.setConstraints(canvas, c) # Button 1: top right c.gridx = 1 c.gridheight = 1 c.fill = GridBagConstraints.HORIZONTAL c.weightx = 1.0 gridbag.setConstraints(button, c) # Button 2: bottom right c.gridy = 1 gridbag.setConstraints(button2, c) frame.pack()
- A GUI with a JTable listing image file paths in its rows. We add a MouseListener so that, when we double-click on a row, we open the image in a separate thread to avoid blocking the UI.
Later, we add a textfield at the bottom for filtering paths using regular expressions. And also use GridBagLayout with specific GridBagConstraints to ensure that the table and the text field are resized appropriately when resizing the window. The table is given a weight of 1.0 so that it takes all available free space, with the text field only resizing horizontally.
from __future__ import with_statement from javax.swing.table import AbstractTableModel import os from javax.swing import JFrame from javax.swing import JPanel from java.awt import Dimension from javax.swing import JScrollPane from java.awt.event import MouseAdapter from ij import IJ from java.lang import Thread from java.awt import GridBagLayout from java.awt import GridBagConstraints from javax.swing import JTextField from java.awt.event import KeyEvent from javax.swing import SwingUtilities import re filepaths =  filtered_filepaths =  class TableModel(AbstractTableModel): def getColumnCount(self): return 2 def getRowCount(self): return len(filtered_filepaths) def getColumnName(self, index): if 0 == index: return "Index" if 1 == index: return "Filepath" return "" def setValueAt(self, value, rowIndex, colIndex): pass def isCellEditable(self, rowIndex, colIndex): return False def getValueAt(self, rowIndex, colIndex): if 0 == colIndex: return rowIndex + 1 if 1 == colIndex: return filtered_filepaths[rowIndex] return "" table = JTable(TableModel()) txt_file = "/home/albert/LarvalScreen.txt" base_path = "/home/albert/LarvalScreen/" with open(txt_file, 'r') as f: filepaths = [line[:-1] for line in f] frame = JFrame("collection") panel = JPanel() jsp = JScrollPane(table) jsp.setPreferredSize(Dimension(600, 600)) panel.add(jsp) frame.getContentPane().add(panel) frame.pack() frame.setVisible(True) filtered_filepaths = filepaths class RowClickListener(MouseAdapter): def mousePressed(self, event): if 2 == event.getClickCount(): rowIndex = table.rowAtPoint(event.getPoint()) image_file_path = os.path.join(base_path, table.getModel().getValueAt(rowIndex, 1)) def openImage(): IJ.open(image_file_path) t = Thread(openImage) t.setPriority(Thread.NORM_PRIORITY) t.start() table.addMouseListener(RowClickListener()) gb = GridBagLayout() panel.setLayout(gb) c = GridBagConstraints() c.fill = GridBagConstraints.BOTH c.weightx = 1.0 c.weighty = 1.0 gb.setConstraints(jsp, c) frame.pack() def updateTable(): table.updateUI() def filterTable(event): # event is a KeyEvent global filtered_filepaths keyCode = event.getKeyCode() if KeyEvent.VK_ENTER == keyCode: # Now filter the content of the table with the text text = event.getSource().getText() #filtered_filepaths = [path for path in filepaths if path.find(text) > -1] try: pattern = re.compile(text) filtered_filepaths = [path for path in filepaths if re.search(pattern, path)] SwingUtilities.invokeLater(updateTable) except: IJ.error("Check your regular expression! It's wrong.") # Regular expression basics (regex) # ^ : beginning of the text # . : match any character # * : modifier for matching zero or more of the previous character # + : modifier for matching one or more of the previous character # $ : end of the text # # 72F11.*lsm$ filter_field = JTextField("", keyPressed=filterTable) c.gridx = 0 c.gridy = 1 c.fill = GridBagConstraints.HORIZONTAL c.anchor = GridBagConstraints.NORTHWEST c.weighty = 0.0 gb.setConstraints(filter_field, c) panel.add(filter_field) frame.pack()
Towards the end, I also touched briefly on WEKA machine learning segmentation. The script that I used, which generated synthetic data for electron microscopy images of nervous tissue, is here:
The above had the issue that the imglib2-algorithm branch ImgMath-blockread may not have yet been released into the Fiji updater. Therefore I had to compile it with mvn like this:
$ cd ~/workspace/imglib2-algorithm/ $ mvn -Dimagej.app.directory=/home/albert/Fiji.app/ -Dmaven.test.skip=true -Denforcer.skip=true -Dimglib2.version=LATEST clean install $ rm ~/home/albert/Fiji.app/jars/imglib2-algorithm-0.* $ mv target/`imglib2-algorithm-0.11.3`-SNAPSHOT.jar ~/home/albert/Fiji.app/jars/
… and restart Fiji.
In addition, here is some documentation that I had shared earlier for the participants of the workshop:
- Swiftly navigate potentially vast collections of image files, by first collecting all file paths into a text files, and then accessing such list on a custom graphical user interface, where file paths are displayed on a table and filtered via regular expressions, and the images opened simply by double-click on a table row:
- Keep an annotated record of which files you’ve opened and when did you first and last did so, with notes written in for every file, and the table of opened files being filterable with regular expressions on the notes and the file path:
- Loading potentially very large images by loading only a small window and then panning it in 2D and browsing the Z axis. This is done by expressing the large file as a lazy ImgLib2 data structure, which only loads data on demand and caches it for optimally revisiting recently seen areas of the image:
- Express entire folder of 3D stacks as a 4D ImgLib2 volume, and export to N5 and compare loading from N5
Don’t miss: how to create your own libraries, so that you can reuse functions between scripts. see:
“Organize scripts into libraries for reuse from other scripts”
The above was all explored using jython: a python implementation for the java virtual machine, and which is currently at version 2.7 (the old style python; version 3 is being developed as we speak).
Some useful documentation links:
The python language, version 2.7:
The definitive guide to Jython:
Note in particular:
- Chapter 2: Data Types and Referencing, at collections and the jarray package
- Chapter 16: GUI applications
Fiji python tutorial:
Javadocs (documentation for java code) for Fiji libraries, in particular:
Onward to hacking your own programs to tame the firehose of bioimagery!