Scripts typed in for I2K 2020 "Exploring large volumetric data with Fiji Python scripting" workshop

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.

  1. 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)



  1. 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 ImagePlus.getImageListeners.)

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[0])

# To discover a field named similar to "listener"
for field in ImagePlus.getDeclaredFields():
  if field.getName().lower().find("listener") > -1:
    print field



  1. 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()



  1. 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:

  1. 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:

(See also: https://github.com/acardona/scripts/blob/dcf911d74cc726f5e9f2a9cae1e991b571175458/python/imagej/tabulate_filepaths.py )

  1. 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:

(see also: https://github.com/acardona/scripts/blob/dcf911d74cc726f5e9f2a9cae1e991b571175458/python/imagej/examples/file_open_history_with_notes.py )

  1. 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:

(See also: https://github.com/acardona/scripts/blob/dcf911d74cc726f5e9f2a9cae1e991b571175458/python/imagej/examples/sections_raw_read_piece-wise_as_cachedcellimg.py )

  1. Express entire folder of 3D stacks as a 4D ImgLib2 volume, and export to N5 and compare loading from N5

(See also: https://github.com/acardona/scripts/blob/dcf911d74cc726f5e9f2a9cae1e991b571175458/python/imagej/examples/imglib2_load_4D_CachedCellImg_from_3D_filepaths.py )



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”
https://syn.mrc-lmb.cam.ac.uk/acardona/fiji-tutorial/#library

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:

Onward to hacking your own programs to tame the firehose of bioimagery!

19 Likes

Excellent work. Powerful tools with high versatility. Thanks Albert.

1 Like

You are welcome! And we barely scratched the surface, there is so much in Fiji.

1 Like

Hi @albertcardona,

The ImageJ 1.53g60 daily build adds an ImagePlus.getListeners() method. ImageJ 1.53g also adds Python support to the recorder.

5 Likes