imagej之Python腳本

原生ImageJ僅支持JS腳本,而ImageJ的衍生版本Fiji支持Python腳本編程,所以這裏的ImageJ實際是Fiji。
本文是對這個Tutorial的翻譯。
Fiji官方的Jython指南在這裏

上手

有兩種方式可以打開腳本編輯器:

  • 通過File-New-Script打開。
  • 使用Command finder:具體就是按字母“l”,然後輸入script,然後選擇下面的script。

打開編輯器後,選擇Language爲Python。

你的第一個Fiji腳本

首先隨便打開一個圖片。

獲取打開的圖片

在編輯器中輸入以下代碼:

from ij import IJ

imp = IJ.getImage()
print imp

注意:從下面可以看到,也可以通過IJ.openImage直接在腳本中獲取某個未打開的圖像。然後點擊“Run”,或者使用快捷鍵“Ctrl+R”,這樣程序就會運行,且在下方的窗口中打印出結果。
值得一提的是,imp是一個常用的名字,用來表示ImagePlus類的一個實例。ImagePlus是ImageJ表示一張圖片的抽象,實際上在ImageJ中打開的每一個圖像都是一個ImagePlus的實例。

通過一個文件對話框保存圖像

這裏在圖像上的第一個操作是“保存”它。
在編輯器中增加以下代碼:

from ij.io import FileSaver
fs = FileSaver(imp)
fs.save()

這部分代碼是導入FileSaver這個命名空間,然後創建一個參數爲上面imp的FileSaver的實例,然後調用它的save函數。

將圖像直接寫入文件

編寫腳本的一個目的是避免與人交互,因此這裏直接將圖像寫入文件中:

folder = " "
filepath = folder + "boats.tif"
fs.saveAsTiff(filepath)

這裏指定了文件保存路徑,然後調用FileSaver的saveAsTiff方法,同理還有saveAsPng、saveAsJpeg等。

保存文件時事先檢查路徑和文件

FileSaver將會覆蓋給定路徑下的文件,因此需要事先檢查一下,防止誤操作。

folder = " "

from os import path

if path.exists(folder) and path.isdir(folder):
	print "folder exists:", folder
	filepath = path.join(folder, "boats.tif")
	if path.exists(filepath):
		print "File exists! Not saving the image, would overwrite a file!"
	elif fs.saveAsTiff(filepath):
		print "File saved successfully at ", filepath	
else:
	print "Folder does not exist or it's not a folder!"

首先檢查上面的folder是否存在及它是否是一個路徑;然後檢查該路徑下是否有與要寫入的文件有相同名字的文件,如果有,則提示,且不覆蓋;最後檢查FileSaver的saveAsTiff函數是否工作正常,如果該方法工作正常,將會返回true,因此這裏會打印成功提示,否則則報錯。

探究圖像的屬性和像素

如前所述,ImageJ或Fiji中的一張圖像實際是ImagePlus類的一個實例。

輸出圖片基本信息

from ij import IJ, ImagePlus

imp = IJ.getImage()

print "title:", imp.title
print "width:", imp.width
print "height:", imp.height
print "number of pixels:", imp.width*imp.height
print "number of slices:", imp.getNSlices()
print "number of channels:", imp.getNChannels()
print "number of time frames:", imp.getNFrames()

types = {ImagePlus.COLOR_RGB: "RGB",
	ImagePlus.GRAY8: "8-bit",
	ImagePlus.GRAY16: "16-bit",
	ImagePlus.GRAY32: "32-bit",
	ImagePlus.COLOR_256: "8-bit color"}

print "image type:", types[imp.type]

ImagePlus包含諸如圖像標題、尺寸(寬度、高度、slices數目、時間幀數目、通道數目),以及像素(被包裝在ImageProcessor實例中)。這些數據都是ImagePlus的field,可以通過點號來獲取,如img.title就能得到圖像標題,如果沒有發現某些fields,可以通過get方法得到,如getNChannels得到通道數。
對於image的type,上述代碼是定義了一個types字典,這樣就通過鍵值對的方式更加直觀地顯示image的type。

獲得圖像的像素統計

ImageJ/Fiji提供了ImageStatistics類做像素統計,其中又使用了這個類的getStatistics的靜態方法。

from ij import IJ
from ij.process import ImageStatistics as IS

imp = IJ.getImage()

ip = imp.getProcessor()

options = IS.MEAN | IS.MEDIAN | IS.MIN_MAX
stats = IS.getStatistics(ip, options, imp.getCalibration())

print "Image statistics for", imp.title
print "Mean:", stats.mean
print "Median:", stats.median
print "Min and max:", stats.min, "-", stats.max

注意,用getProcessor得到像素信息。如果圖像是stacks,那麼使用StackStatistics。

如果是很多圖片,該怎樣做統計?可以像下面這樣寫一個函數:

from ij import IJ
from ij.process import ImageStatistics as IS
import os

options = IS.MEAN | IS.MEDIAN | IS.MIN_MAX

def getStatistics(imp):
	""" Return statistics for the given ImagePlus """
	global options
	ip = imp.getProcessor()	
	stats = IS.getStatistics(ip, options, imp.getCalibration())	
	return stats.mean, stats.median, stats.min, stats.max


folder = " "

for filename in os.listdir(folder):
	if filename.endswith(".png"):
		print "Processing", filename
		imp = IJ.openImage(os.path.join(folder, filename))
		if imp is None:
			print "Could not open image from file:", filename
			continue
		mean, median, min, max = getStatistics(imp)
		print "Image statistics for", imp.title
		print "Mean:", mean
		print "Median:", median
		print "Min and max:", min, "-", max
	else:
		print "Ignoring", filename

 

像素迭代

像素迭代是一個低層操作,很少用到。但如果真需要用到,下面提供了三種迭代方式:

from java.lang import Float
from ij import IJ

imp = IJ.getImage()

ip = imp.getProcessor().convertToFloat()
pixels = ip.getPixels()

print "Image is", imp.title, "of type", imp.type

# Method 1: the for loop, C style
minimum = Float.MAX_VALUE
for i in xrange(len(pixels)):
	if pixels[i] < minimum:
		minimum = pixels[i]
print "1. Minimum is:", minimum

# Method 2: iterate pixels as a list
minimum = Float.MAX_VALUE
for pix in pixels:
	if pix < minimum:
		minimum = pix
print "2. Minimum is:", minimum

# Method 3: apply the built-in min function
# to the first pair of pixels,
# and then to the result of that and the next pixel, etc.
minimum = reduce(min, pixels)
print "3. Minium is:", minimum

對於上面的pixels,根據不同類型的圖像,可以有不同的形式,具體可以打印出來看一下。對於RGB圖像,可以調用getBrightness來看看哪個像素最亮,可以通過ip.toFloat(0,None)來獲得第一個通道。
上面使用了三種風格來處理像素列表,一種是C風格的形式,第二種是列表形式,第三種是函數式編程風格。

對列表或集合的迭代或循環操作

對於上面三種處理列表的風格,前兩種都使用了for循環來進行迭代,好處是特別容易理解,壞處是性能上會有點弱,且不簡潔;第三種採用reduce這種函數式編程的風格,其他還有map、filter等操作,好處是性能好且形式簡潔,但壞處是較難理解。
下面通過幾個例子更詳細地介紹這幾種風格的對比。

map操作

map操作接收一個長度爲N的列表,然後施加一個函數,返回一個長度也爲N的列表。比如,你想得到Fiji中打開的一系列的圖片,如果用for循環,需要先顯式創建一個列表,然後一個一個地把圖片附加進去。
如果使用python的列表解析的語法糖,列表可以直接創建,然後在中括號裏直接寫上for循環的邏輯。因此,本質上它仍然是個序列化的循環,無法並行操作。
通過使用map,則可直接在ID列表中對每個ID進行WM.getImage作用。

from ij import WindowManager as WM

# Method 1: with a for loop
images = []
for id in WM.getIDList():
	images.append(WM.getImage(id))

# Method 2: with list comprehension
images = [WM.getImage(id) for id in WM.getIDList()]

# Method 3: with a map openration
images = map(WM.getImage, WM.getIDList())

filter操作

filter操作接收一個長度爲N的列表,然後返回一個更短的列表,只有通過特定條件的元素才能進入到新列表中。比如下面這個列子就是找到Fiji打開的圖片中標題中有特定字符串的圖片。

from ij import WindowManager as WM

imps = map(WM.getImage, WM.getIDList())

def match(imp):
	""" Returns true if the image title contains the word 'boat' """
	return imp.title.find("boat") > -1

# Method 1: with a for loop
matching = []
for imp in imps:
	if match(imp):
		matching.append(imp)

# Method 2: with list comprehension
matching = [imp for imp in imps if match(imp)]

# Method 3: with a filter operation
matching = filter(match, imps)

reduce操作

reduce操作接收一個長度爲N的列表,然後返回一個單值。比如你想找到Fiji當前打開的圖片中面積最大的那個。如果使用for循環,需要設置臨時變量,以及判斷列表中是否有至少一個元素等,而使用reduce則不需要臨時變量,只需要一個輔助函數(其實甚至這裏只需要一個匿名lambda函數,但這裏顯式定義,從而更好理解及可用性更好)。

from ij import IJ
from ij import WindowManager as WM

imps = map(WM.getImage, WM.getIDList())

def area(imp):
	return imp.width * imp.height

# Method 1: with a for loop
largest = None
largestArea = 0
for imp in imps:
	if largest is None:
		largest = imp
	else:
		a = area(imp)
		if a > largestArea:
			largest = imp
			largestArea = a

# Method 2: with a reduce operation
def largestImage(imp1, imp2):
	return imp1 if area(imp1) > area(imp2) else imp2

largest = reduce(largestImage, imps)

對每一個像素減去最小值

首先使用上面的reduce方法得到最小的像素值,然後使用下面兩種方法對每個像素減去該最小值:

from ij import IJ, ImagePlus
from ij.process import FloatProcessor

imp = IJ.getImage()
ip = imp.getProcessor().convertToFloat()
pixels = ip.getPixels()

minimum = reduce(min, pixels)

# Method 1: subtract the minimum from every pixel
# in place, modifying the pixels array
for i in xrange(len(pixels)):
	pixels[i] -= minimum

imp2 = ImagePlus(imp.title, ip)
imp2.show()

# Method 2: subtract the minimum from every pixel
# and store the result in a new array
pixels3= map(lambda x: x-minimum, pixels)
ip3 = FloatProcessor(ip.width, ip.height, pixels3, None)
imp3 = ImagePlus(imp.title, ip3)
imp3.show()

將像素列表概括爲單值

統計在閾值以上的像素個數

本例將計算有多少像素在特定閾值之上,使用reduce函數。

from ij import IJ

imp = IJ.getImage()
ip = imp.getProcessor().convertToFloat()
pixels = ip.getPixels()

mean = sum(pixels) / len(pixels)

n_pix_above = reduce(lambda count, a: count+1 if a > mean else count, pixels, 0)

print "Mean value", mean
print "% pixels above mean:", n_pix_above / float(len(pixels)) * 100

統計在閾值以上的所有像素的座標,並計算重心

所有像素值在閾值(這裏是均值)以上的座標點都通過filter函數收集在一個列表中。注意,在ImageJ中,一張圖像的所有像素都存儲在一個線性數組中,數組的長度是width與height的乘積。因此,某個像素的索引除以width的餘數(即模)是該像素的X座標,索引除以width的商是該像素的Y座標。
同時,給出了四種計算中心的方法,第一種是使用for循環,第二種是使用map和sum,第三種是使用reduce,第四種是對第三種的簡化,使用了functools包中的partial函數。

from ij import IJ

imp = IJ.getImage()
ip = imp.getProcessor().convertToFloat()
pixels = ip.getPixels()

mean = sum(pixels) / len(pixels)

above = filter(lambda i: pixels[i] > mean, xrange(len(pixels)))

print "Number of pixels above mean value:", len(above)

width = imp.width

# Method 1: with a for loop
xc = 0
yc = 0
for i in above:
	xc += i % width
	yc += i / width
xc = xc / len(above)
yc = yc / len(above)
print xc, yc

# Method 2: with sum and map
xc = sum(map(lambda i: i % width, above)) / len(above)
yc = sum(map(lambda i: i / width, above)) / len(above)
print xc, yc

# Method 3: iterating the list "above" just once
xc, yc = [d / len(above) for d in 
		reduce(lambda c, i: [c[0] + i%width, c[1] + i/width], above, [0,0])]
print xc, yc

# Method 4: iterating the list "above" just once, more clearly and performant
from functools import partial

def accum(width, c, i):
	c[0] += i % width
	c[1] += i / width
	return c
xc, yc = [d/len(above) for d in reduce(partial(accum, width), above, [0,0])]
print xc, yc

運行ImageJ/Fiji插件

下面是一個對當前圖片施加中值濾波的例子:

from ij import IJ, ImagePlus
from ij.plugin.filter import RankFilters

imp = IJ.getImage()
ip = imp.getProcessor().convertToFloat()

radius = 2
RankFilters().rank(ip, radius, RankFilters.MEDIAN)

imp2 = ImagePlus(imp.title + " median filtered", ip)
imp2.show()

查找哪個類用了哪個插件

現在的問題是怎樣知道上面的中值濾波在RankFilters中。可以通過Command Finder來查找。

查找插件所需要的參數

可以通過Macro Recorder來查看一個插件必要的參數。

  • 打開Plugins-Macros-Record
  • 運行命令,比如上面的Process-Filters-Median。此時會出現一個對話框,要求輸入半徑,點擊OK
  • 查看Recorder窗口中的顯示:run(“Median…”, “radius=2”);

這說明中值濾波的一個必要參數就是radius。

運行命令command

上面的插件plugin其實可以通過剛纔錄製的宏命令來替代:

IJ.run(imp, "Median...", "radius=2")

創建圖像及其ROI

從零開始創建一張圖像

一張ImageJ/Fiji圖像至少包含三個部分:

  • 像素數組:一個含有原始類型數據的數組,數據類型可以是byte、short、int或float
  • ImageProcessor子類實例:用來承載像素數組
  • ImagePlus實例:用來承載ImageProcessor實例

下面的例子中創建了一個空的float型數組,然後用隨機的浮點數填充,然後把它傳給FloatProcessor,最後傳給ImagePlus:

from ij import ImagePlus
from ij.process import FloatProcessor
from array import zeros
from random import random

width = 1024
height = 1024
pixels = zeros('f', width * height)

for i in xrange(len(pixels)):
	pixels[i] = random()

fp = FloatProcessor(width, height, pixels, None)
imp = ImagePlus("White noise", fp)

imp.show()

用給定值填充一個ROI

爲了填充一個ROI,我們可以對像素進行迭代,知道在ROI內部的像素,然後將它們的值設爲指定值。但這個過程是繁雜且容易出錯。更有效的方式是創建一個Roi類的實例或它的子類PolygonRoi、OvalRoi、ShapeRoi等的實例,然後告訴ImageProcessor來填充這個區域。
下面的例子使用了上面創建的白噪聲圖像,然後在其中定義了一個矩形區域,然後用2填充。

from ij.gui import Roi, PolygonRoi

roi = Roi(400, 200, 400, 300)
fp.setRoi(roi)
fp.setValue(2.0)
fp.fill()

imp.show()

因爲原來的白噪聲的值是0到1的隨機數,現在加上的值是2,是新的最大值,所以看起來是白色的。
在上面的代碼基礎上再增加一個新的多邊形區域,用-3填充:

xs = [234, 174, 162, 102, 120, 123, 153, 177, 171,  
      60, 0, 18, 63, 132, 84, 129, 69, 174, 150,  
      183, 207, 198, 303, 231, 258, 234, 276, 327,  
      378, 312, 228, 225, 246, 282, 261, 252]  
ys = [48, 0, 60, 18, 78, 156, 201, 213, 270, 279,  
      336, 405, 345, 348, 483, 615, 654, 639, 495,  
      444, 480, 648, 651, 609, 456, 327, 330, 432,  
      408, 273, 273, 204, 189, 126, 57, 6]
proi = PolygonRoi(xs, ys, len(xs), Roi.POLYGON)
fp.setRoi(proi)
fp.setValue(-3)
fp.fill(proi.getMask()) # Attention here!

imp.show()

創建和操作圖像的stacks和hyperstacks

加載一張多彩圖像stack並提取綠色通道

from ij import IJ, ImagePlus, ImageStack

imp = IJ.openImage("/home/qixinbo/temp/flybrain.zip")
stack = imp.getImageStack()

print "Number of slices:", imp.getNSlices()

greens = []
for i in xrange(1, imp.getNSlices()+1):
	cp = stack.getProcessor(i) # Get the ColorProcessor slice at index i
	fp = cp.toFloat(1, None) # Get its green channel as a FloatProcessor
	greens.append(fp) # store it in a list

stack2 = ImageStack(imp.width, imp.height) # Create a new stack with only the green channel
for fp in greens:
	stack2.addSlice(None, fp)

imp2 = ImagePlus("Green channel", stack2) # Create a new image with stack of green channel slices
IJ.run(imp2, "Green", "") # Set a green look-up table
imp2.show()

首先加載名爲”Fly Brain”的樣例。然後對它的slices進行迭代。每個slices都是一個ColorProcessor,它有一個toFloat的方法,可以得到一個特定顏色通道的FloatProcessor。將顏色通道用浮點數表示是最方便的處理像素值的方法,它不像字節類型那樣會溢出。最後一個創建綠色的LUT命令,可以通過錄制宏命令的形式得到具體的寫法。如果不加這個綠色的LUT,整個圖像雖然是綠色通道的像素值,但是以灰度圖呈現的。

將一個RGB stack轉換爲雙通道的32-bit hyperstack

from ij import IJ, ImagePlus, ImageStack, CompositeImage

imp = IJ.openImage("/home/qixinbo/temp/flybrain.zip")
stack = imp.getImageStack()

stack2 = ImageStack(imp.width, imp.height) # Create a new stack with only the green channel

for i in xrange(1, imp.getNSlices()+1):
	cp = stack.getProcessor(i) # Get the ColorProcessor slice at index i
	red = cp.toFloat(0, None) # Get the red channel as a FloatProcessor
	green = cp.toFloat(1, None) # Get its green channel as a FloatProcessor
	stack2.addSlice(None, red)
	stack2.addSlice(None, green)

imp2 = ImagePlus("32-bit 2-channel composite", stack2)
imp2.setCalibration(imp.getCalibration().copy())

nChannels = 2
nSlices = stack.getSize()
nFrames = 1
imp2.setDimensions(nChannels, nSlices, nFrames)
comp = CompositeImage(imp2, CompositeImage.COLOR)
comp.show()

其中比較重要的是告訴hyperstack怎樣解釋它的圖像,即setDimensions中的參數,即有多少個通道、多少個slices和多少個時間幀。

人機交互:文件和選項對話框、消息、進度條

詢問打開文件夾

from ij.io import DirectoryChooser

dc = DirectoryChooser("Choose a folder")
folder = dc.getDirectory()

if folder is None:
	print "User cancelled the dialog!"
else:
	print "Selected folder:", folder

詢問打開文件

from ij.io import OpenDialog

od = OpenDialog("Choose a file", None)
filename = od.getFileName()

if filename is None:
	print "User canceled the dialog!"
else:
	directory = od.getDirectory()
	filepath = directory + filename
	print "Selected file path:", filepath

詢問輸入參數

from ij.gui import GenericDialog

def getOptions():
	gd = GenericDialog("Options")
	gd.addStringField("name", "Untitled")
	gd.addNumericField("alpha", 0.25, 2)
	gd.addCheckbox("Optimize", True)
	types = ["8-bit", "16-bit", "32-bit"]
	gd.addChoice("Output as", types, types[2])
	gd.addSlider("Scale", 1, 100, 100)
	gd.showDialog()

	if gd.wasCanceled():
		print "User canceled dialog!"
		return

	name = gd.getNextString()
	alpha = gd.getNextNumber()
	optimize = gd.getNextBoolean()
	output = gd.getNextChoice()
	scale = gd.getNextNumber()
	return name, alpha, optimize, output, scale

options = getOptions()
if options is not None:
	name, alpha, optimize, output, scale = options
	print name, alpha, optimize, output, scale

顯示進度條

1
2
3
4
5
6
7
8
9
10
from ij import IJ

imp = IJ.getImage()
stack = imp.getImageStack()

for i in xrange(1, stack.getSize()+1):
	IJ.showProgress(i, stack.getSize()+1)
	ip = stack.getProcessor(i)

IJ.showProgress(1)

將腳本轉換爲插件

將腳本存儲在Fiji插件文件夾或其子文件夾下,注意:

  • 文件名中要有一個下劃線
  • 後綴是.py

比如”my_script.py”。然後運行“Help-Update Menus”,或者重啓Fiji。
腳本就會作爲”Plugins”中的菜單命令出現,也可以使用Command Finder找到。
插件文件夾在哪呢:對於Ubuntu和Windows系統,在Fiji.app文件夾中。

列表、原生數組及與Java類的交互

Jython列表以只讀數組的形式傳給Java類

from java.awt.geom import AffineTransform
from array import array

x = 10
y = 40

aff = AffineTransform(1, 0, 0, 1, 45, 56)

p = [x, y]
aff.transform(p, 0, p, 0, 1)
print p

q = array('f', [x, y])
aff.transform(q, 0, q, 0, 1)
print q

上面的p沒有被更新,而q被更新了,所以Jython列表只是可讀,而array類型可寫。

創建原生數組

array包有兩個函數:

  • zeros:創建空的原生數組
  • array:從列表或另一個相同類型的array中創建數組

array的第一個參數是type。對於原生類型,如char、short、int、float、long和double,可以使用帶引號的單個字母。下面的例子中創建了ImagePlus類型的array。

from ij import IJ
from array import array, zeros
from ij import ImagePlus

a = zeros('f', 5)
print a

b = array('f', [0, 1, 2, 3, 4])
print b

imps = zeros(ImagePlus, 5) # An empty native ImagePlus array of length 5
print imps

imps[0] = IJ.getImage() # Assign the current image to the first element of the array
print imps

print "length:", len(imps)

可作用於任意類型圖像的通用算法:ImgLib庫

Imglib是一個處理n維數據的通用庫,主要是面向圖像處理。用Imglib編程能夠極大地簡化在不同類型圖片上的操作。

對圖片進行數學操作

主要有以下幾類的操作:

  • script.imglib.math:提供操作每一個像素的函數。這些函數是可組合的,即某個函數的輸出可作爲另一個的輸入。
  • script.imglib.color:提供創建和操縱彩色圖片的函數,比如在RGB色彩空間中提取特定的色彩通道。
  • script.imglib.algorithm:提供諸如Gauss、Scale3D、Affine3D、Resample、Downsample等函數,一次對多個像素進行操作,而不是逐個像素的操作。一些函數可能改變圖像的尺寸。這些算法全部都返回圖像,換句話說,是對原始圖像施加這些算法後得到的新圖像。
  • script.imglib.analysis:提供提取或測量圖像或者評價圖像的函數。
from script.imglib.math import Compute, Subtract
from script.imglib.color import Red, Green, Blue, RGBA
from script.imglib import ImgLib
from ij import IJ

imp = IJ.openImage("/home/qixinbo/temp/flybrain.zip")

img = ImgLib.wrap(imp) # Wrap it as an Imglib image

sub = Compute.inFloats(Subtract(Green(img), Red(img)))

ImgLib.wrap(sub).show()

rgb = RGBA(Red(img), Subtract(Green(img), Red(img)), Blue(img)).asImage()

ImgLib.wrap(rgb).show()

使用圖像數學進行平場校正

from script.imglib.math import Compute, Subtract, Divide, Multiply
from script.imglib.algorithm import Gauss, Scale2D, Resample
from script.imglib import ImgLib
from ij import IJ

img = ImgLib.wrap(IJ.openImage("/home/qixinbo/temp/bridge.gif"))

brightfield = Resample(Gauss(Scale2D(img, 0.25), 20), img.getDimensions())  
  
# 3. Simulate a perfect darkfield  
darkfield = 0  
  
# 4. Compute the mean pixel intensity value of the image  
mean = reduce(lambda s, t: s + t.get(), img, 0) / img.size()  
  
# 5. Correct the illumination  
corrected = Compute.inFloats(Multiply(Divide(Subtract(img, brightfield),  
                                             Subtract(brightfield, darkfield)), mean))  
  
# 6. ... and show it in ImageJ  
ImgLib.wrap(corrected).show()

提取和操縱圖像色彩通道:RGBA和HSB

下面的代碼是對RGBA色彩空間的操作:

from script.imglib.math import Compute, Subtract, Multiply  
from script.imglib.color import Red, Blue, RGBA  
from script.imglib.algorithm import Gauss, Dither
from script.imglib import ImgLib
from ij import IJ  
  
# Obtain a color image from the ImageJ samples    
clown = ImgLib.wrap(IJ.openImage("https://imagej.nih.gov/ij/images/clown.jpg"))  
    
# Example 1: compose a new image manipulating the color channels of the clown image:    
img = RGBA(Gauss(Red(clown), 10), 40, Multiply(255, Dither(Blue(clown)))).asImage()    
    
ImgLib.wrap(img).show()

下面的代碼是對HSB色彩空間的操作:

from script.imglib.math import Compute, Add, Subtract  
from script.imglib.color import HSB, Hue, Saturation, Brightness  
from script.imglib import ImgLib  
from ij import IJ  
  
# Obtain an image  
img = ImgLib.wrap(IJ.openImage("https://imagej.nih.gov/ij/images/clown.jpg"))  
  
# Obtain a new clown, whose hue has been shifted by half  
# with the same saturation and brightness of the original  
bluey = Compute.inRGBA(HSB(Add(Hue(img), 0.5), Saturation(img), Brightness(img)))  
  
ImgLib.wrap(bluey).show()

下面的代碼是對一個RGB confocal stack進行伽馬校正:

# Correct gamma  
from script.imglib.math import Min, Max, Exp, Multiply, Divide, Log  
from script.imglib.color import RGBA, Red, Green, Blue  
from script.imglib import ImgLib
from ij import IJ  
  
gamma = 0.5  
img = ImgLib.wrap(IJ.openImage("/home/qixinbo/temp/flybrain.zip"))  
  
def g(channel, gamma):  
  """ Return a function that, when evaluated, computes the gamma 
        of the given color channel. 
      If 'i' was the pixel value, then this function would do: 
      double v = Math.exp(Math.log(i/255.0) * gamma) * 255.0); 
      if (v < 0) v = 0; 
      if (v >255) v = 255; 
  """  
  return Min(255, Max(0, Multiply(Exp(Multiply(gamma, Log(Divide(channel, 255)))), 255)))  
  
corrected = RGBA(g(Red(img), gamma), g(Green(img), gamma), g(Blue(img), gamma)).asImage()  
  
ImgLib.wrap(corrected).show()

通過高斯差尋找、計數和顯示三維stack中的cell

# Load an image of the Drosophila larval fly brain and segment  
# the 5-micron diameter cells present in the red channel.  
  
from script.imglib.analysis import DoGPeaks  
from script.imglib.color import Red  
from script.imglib.algorithm import Scale2D  
from script.imglib.math import Compute  
from script.imglib import ImgLib  
from ij3d import Image3DUniverse  
from javax.vecmath import Color3f, Point3f  
from ij import IJ  
  
cell_diameter = 5  # in microns  
minPeak = 40 # The minimum intensity for a peak to be considered so.  
imp = IJ.openImage("http://samples.fiji.sc/first-instar-brain.zip")  
  
# Scale the X,Y axis down to isotropy with the Z axis  
cal = imp.getCalibration()  
scale2D = cal.pixelWidth / cal.pixelDepth  
iso = Compute.inFloats(Scale2D(Red(ImgLib.wrap(imp)), scale2D))  
  
# Find peaks by difference of Gaussian  
sigma = (cell_diameter  / cal.pixelWidth) * scale2D  
peaks = DoGPeaks(iso, sigma, sigma * 0.5, minPeak, 1)  
print "Found", len(peaks), "peaks"  
  
# Convert the peaks into points in calibrated image space  
ps = []  
for peak in peaks:  
  p = Point3f(peak)  
  p.scale(cal.pixelWidth * 1/scale2D)  
  ps.append(p)  
  
# Show the peaks as spheres in 3D, along with orthoslices:  
univ = Image3DUniverse(512, 512)  
univ.addIcospheres(ps, Color3f(1, 0, 0), 2, cell_diameter/2, "Cells").setLocked(True)  
univ.addOrthoslice(imp).setLocked(True)  
univ.show()

ImgLib2:編寫通用且高性能的圖像處理程序

ImgLib2是一個非常強大的圖像處理庫:

ImgLib2對於圖像的Views

from ij import IJ  
from net.imglib2.img.display.imagej import ImageJFunctions as IL  
from net.imglib2.view import Views  
  
# Load an image (of any dimensions) such as the clown sample image  
imp = IJ.getImage()  
  
# Convert to 8-bit if it isn't yet, using macros  
IJ.run(imp, "8-bit", "")  
  
# Access its pixel data from an ImgLib2 RandomAccessibleInterval  
img = IL.wrapReal(imp)  
  
# View as an infinite image, with a value of zero beyond the image edges  
imgE = Views.extendZero(img)  
  
# Limit the infinite image with an interval twice as large as the original,  
# so that the original image remains at the center.  
# It starts at minus half the image width, and ends at 1.5x the image width.  
minC = [int(-0.5 * img.dimension(i)) for i in range(img.numDimensions())]  
maxC = [int( 1.5 * img.dimension(i)) for i in range(img.numDimensions())]  
imgL = Views.interval(imgE, minC, maxC)  
  
# Visualize the enlarged canvas, so to speak  
imp2 = IL.wrap(imgL, imp.getTitle() + " - enlarged canvas") # an ImagePlus  
imp2.show()

對上面的圖片的邊緣進行填充:

imgE = Views.extendMirrorSingle(img)  
imgL = Views.interval(imgE, minC, maxC)  
  
# Visualize the enlarged canvas, so to speak  
imp2 = IL.wrap(imgL, imp.getTitle() + " - enlarged canvas") # an ImagePlus  
imp2.show()

ImgLib2的高斯差分檢測

from ij import IJ  
from ij.gui import PointRoi  
from ij.measure import ResultsTable  
from net.imglib2.img.display.imagej import ImageJFunctions as IL  
from net.imglib2.view import Views  
from net.imglib2.algorithm.dog import DogDetection  
from jarray import zeros  
  
# Load a greyscale single-channel image: the "Embryos" sample image  
imp = IJ.openImage("https://imagej.nih.gov/ij/images/embryos.jpg")  
# Convert it to 8-bit  
IJ.run(imp, "8-bit", "")  
  
# Access its pixel data from an ImgLib2 data structure: a RandomAccessibleInterval  
img = IL.wrapReal(imp)  
  
# View as an infinite image, mirrored at the edges which is ideal for Gaussians  
imgE = Views.extendMirrorSingle(img)  
  
# Parameters for a Difference of Gaussian to detect embryo positions  
calibration = [1.0 for i in range(img.numDimensions())] # no calibration: identity  
sigmaSmaller = 15 # in pixels: a quarter of the radius of an embryo  
sigmaLarger = 30  # pixels: half the radius of an embryo  
extremaType = DogDetection.ExtremaType.MAXIMA  
minPeakValue = 10  
normalizedMinPeakValue = False  
  
# In the differece of gaussian peak detection, the img acts as the interval  
# within which to look for peaks. The processing is done on the infinite imgE.  
dog = DogDetection(imgE, img, calibration, sigmaSmaller, sigmaLarger,  
  extremaType, minPeakValue, normalizedMinPeakValue)  
  
peaks = dog.getPeaks()  
  
# Create a PointRoi from the DoG peaks, for visualization  
roi = PointRoi(0, 0)  
# A temporary array of integers, one per dimension the image has  
p = zeros(img.numDimensions(), 'i')  
# Load every peak as a point in the PointRoi  
for peak in peaks:  
  # Read peak coordinates into an array of integers  
  peak.localize(p)  
  roi.addPoint(imp, p[0], p[1])  
  
imp.setRoi(roi)  
  
# Now, iterate each peak, defining a small interval centered at each peak,  
# and measure the sum of total pixel intensity,  
# and display the results in an ImageJ ResultTable.  
table = ResultsTable()  
  
for peak in peaks:  
  # Read peak coordinates into an array of integers  
  peak.localize(p)  
  # Define limits of the interval around the peak:  
  # (sigmaSmaller is half the radius of the embryo)  
  minC = [p[i] - sigmaSmaller for i in range(img.numDimensions())]  
  maxC = [p[i] + sigmaSmaller for i in range(img.numDimensions())]  
  # View the interval around the peak, as a flat iterable (like an array)  
  fov = Views.interval(img, minC, maxC)  
  # Compute sum of pixel intensity values of the interval  
  # (The t is the Type that mediates access to the pixels, via its get* methods)  
  s = sum(t.getInteger() for t in fov)  
  # Add to results table  
  table.incrementCounter()  
  table.addValue("x", p[0])  
  table.addValue("y", p[1])  
  table.addValue("sum", s)  
  
table.show("Embryo intensities at peaks")

此時會得到一個table。有兩種方法來將table中的data存入一個CSV文件中:
第一種是直接在Table窗口中,點擊File-Save,這是最簡單的方式了。
第二種是使用python內置的csv庫,在上面的代碼中加入以下代碼:

from __future__ import with_statement  
# IMPORTANT: imports from __future__ must go at the top of the file.  
  
#  
# ... same code as above here to obtain the peaks  
#  
  
from operator import add  
import csv  
  
# The minumum and maximum coordinates, for each image dimension,  
# defining an interval within which pixel values will be summed.  
minC = [-sigmaSmaller for i in xrange(img.numDimensions())]  
maxC = [ sigmaSmaller for i in xrange(img.numDimensions())]  
  
def centerAt(p, minC, maxC):  
  """ Translate the minC, maxC coordinate bounds to the peak. """  
  return map(add, p, minC), map(add, p, maxC)  
  
def peakData(peaks, p, minC, maxC):  
  """ A generator function that returns all peaks and their pixel sum, 
      one at a time. """  
  for peak in peaks:  
    peak.localize(p)  
    minCoords, maxCoords = centerAt(p, minC, maxC)  
    fov = Views.interval(img, minCoords, maxCoords)  
    s = sum(t.getInteger() for t in fov)  
    yield p, s  
  
# Save as CSV file  
with open('/tmp/peaks.csv', 'wb') as csvfile:  
  w = csv.writer(csvfile, delimiter=',', quotechar='"''"', quoting=csv.QUOTE_NONNUMERIC) 
  w.writerow(['x', 'y', 'sum']) 
  for p, s in peakData(peaks, p, minC, maxC): 
    w.writerow([p[0], p[1], s]) 
 
# Read the CSV file into an ROI 
roi = PointRoi(0, 0) 
with open('/tmp/peaks.csv', 'r') as csvfile: 
  reader = csv.reader(csvfile, delimiter=',', quotechar='"')  
  header = reader.next() # advance reader by one line  
  for x, y, s in reader:  
    roi.addPoint(imp, float(x), float(y))  
  
imp.show()  
imp.setRoi(roi)

使用ImgLib2進行圖像變換

以下是使用ImgLib2進行圖像變換,如平移、旋轉、縮放等。

from net.imglib2.realtransform import RealViews as RV  
from net.imglib2.img.display.imagej import ImageJFunctions as IL  
from net.imglib2.realtransform import Scale  
from net.imglib2.view import Views  
from net.imglib2.interpolation.randomaccess import NLinearInterpolatorFactory  
from ij import IJ  
  
# Load an image (of any dimensions)  
imp = IJ.getImage()  
  
# Access its pixel data as an ImgLib2 RandomAccessibleInterval  
img = IL.wrapReal(imp)  
  
# View as an infinite image, with a value of zero beyond the image edges  
imgE = Views.extendZero(img)  
  
# View the pixel data as a RealRandomAccessible  
# (that is, accessible with sub-pixel precision)  
# by using an interpolator  
imgR = Views.interpolate(imgE, NLinearInterpolatorFactory())  
  
# Obtain a view of the 2D image twice as big  
s = [2.0 for d in range(img.numDimensions())] # as many 2.0 as image dimensions  
bigger = RV.transform(imgR, Scale(s))  
  
# Define the interval we want to see: the original image, enlarged by 2X  
# E.g. from 0 to 2*width, from 0 to 2*height, etc. for every dimension  
minC = [0 for d in range(img.numDimensions())]  
maxC = [int(img.dimension(i) * scale) for i, scale in enumerate(s)]  
imgI = Views.interval(bigger, minC, maxC)  
  
# Visualize the bigger view  
imp2x = IL.wrap(imgI, imp.getTitle() + " - 2X") # an ImagePlus  
imp2x.show()

使用ImgLib2旋轉圖片

from net.imglib2.realtransform import RealViews as RV  
from net.imglib2.realtransform import AffineTransform  
from net.imglib2.img.display.imagej import ImageJFunctions as IL  
from ij import IJ  
from net.imglib2.view import Views  
from net.imglib2.interpolation.randomaccess import NLinearInterpolatorFactory  
from java.awt.geom import AffineTransform as Affine2D  
from java.awt import Rectangle  
from Jama import Matrix  
from math import radians  
  
# Load an image (of any dimensions)  
imp = IJ.getImage()  
  
# Access its pixel data as an ImgLib2 RandomAccessibleInterval  
img = IL.wrapReal(imp)  
  
# View as an infinite image, with value zero beyond the image edges  
imgE = Views.extendZero(img)  
  
# View the pixel data as a RealRandomAccessible  
# (that is, accessible with sub-pixel precision)  
# by using an interpolator  
imgR = Views.interpolate(imgE, NLinearInterpolatorFactory())  
  
# Define a rotation by +30 degrees relative to the image center in the XY axes  
# (not explicitly XY but the first two dimensions)  
# by filling in a rotation matrix with values taken  
# from a java.awt.geom.AffineTransform (aliased as Affine2D)  
# and by filling in the rest of the diagonal with 1.0  
# (for the case where the image has more than 2 dimensions)  
angle = radians(30)  
rot2d = Affine2D.getRotateInstance(  
  angle, img.dimension(0) / 2, img.dimension(1) / 2)  
ndims = img.numDimensions()  
matrix = Matrix(ndims, ndims + 1)  
matrix.set(0, 0, rot2d.getScaleX())  
matrix.set(0, 1, rot2d.getShearX())  
matrix.set(0, ndims, rot2d.getTranslateX())  
matrix.set(1, 0, rot2d.getShearY())  
matrix.set(1, 1, rot2d.getScaleY())  
matrix.set(1, ndims, rot2d.getTranslateY())  
for i in range(2, img.numDimensions()):  
  matrix.set(i, i, 1.0)  
  
print matrix.getArray()  
  
# Define a rotated view of the image  
rotated = RV.transform(imgR, AffineTransform(matrix))  
  
# View the image rotated, without enlarging the canvas  
# so we define the interval as the original image dimensions.  
# (Notice the -1 on the max coordinate: the interval is inclusive)  
minC = [0 for i in range(img.numDimensions())]  
maxC = [img.dimension(i) -1 for i in range(img.numDimensions())]  
imgRot2d = IL.wrap(Views.interval(rotated, minC, maxC),  
  imp.getTitle() + " - rot2d")  
imgRot2d.show()  
  
# View the image rotated, enlarging the interval to fit it.  
# (This is akin to enlarging the canvas.)  
# We compute the bounds of the enlarged canvas by transforming a rectangle,  
# then define the interval min and max coordinates by subtracting  
# and adding as appropriate to exactly capture the complete rotated image.  
# Notice the min coordinates have negative values, as the rotated image  
# has pixels now somewhere to the left and up from the top-left 0,0 origin  
# of coordinates.  
bounds = rot2d.createTransformedShape(  
  Rectangle(img.dimension(0), img.dimension(1))).getBounds()  
minC[0] = (img.dimension(0) - bounds.width) / 2  
minC[1] = (img.dimension(1) - bounds.height) / 2  
maxC[0] += abs(minC[0]) -1 # -1 because its inclusive  
maxC[1] += abs(minC[1]) -1  
imgRot2dFit = IL.wrap(Views.interval(rotated, minC, maxC),  
  imp.getTitle() + " - rot2dFit")  
imgRot2dFit.show()

使用ImgLib2處理RGB和ARGB圖片

from net.imglib2.converter import Converters  
from net.imglib2.view import Views  
from net.imglib2.img.display.imagej import ImageJFunctions as IL  
from ij import IJ  

# # Load an RGB or ARGB image  
imp = IJ.getImage()  

# Access its pixel data from an ImgLib2 data structure:  
# a RandomAccessibleInterval<argbtype>  
img = IL.wrapRGBA(imp)  

# Convert an ARGB image to a stack of 4 channels: a RandomAccessibleInterval<unsignedbyte>  
# with one more dimension that before.  
# The order of channels in the stack can be changed by changing their indices.  
channels = Converters.argbChannels(img, [0, 1, 2, 3])  

# Equivalent to ImageJ's CompositeImage: channels are separate  
impChannels = IL.wrap(channels, imp.getTitle() + " channels")  
impChannels.show()  

# Read out a single channel directly  
red = Converters.argbChannel(img, 1)  

# Alternatively, pick a view of the red channel in the channels stack.  
# Takes the last dimension, which are the channels,  
# and fixes it to just one: that of the red channel (1) in the stack.  
red = Views.hyperSlice(channels, channels.numDimensions() -1, 1)  

impRed = IL.wrap(red, imp.getTitle() + " red channel")  
impRed.show()

Image Calculator:在兩張或多張圖片間進行逐像素操作

ImageJ有一個內置的命令,叫Image Calculator,在Process- Image Calculator下,可以對兩張圖片進行數學操作,比如從一張圖片中減去另一張。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章