之前的一篇文章介绍了利用for循环进行批量处理和储存:
Treasure琛:ImageJ开发教程——循环和存储zhuanlan.zhihu.comfor循环可以解决平时80%以上的问题,但对于某些特殊情况,利用for循环就难以解决了。
例如,当我们想对一个文件夹中的所有图片进行某些操作时,通常会使用循环:
path = getDirectory("Choose Source Directory ");
list = getFileList(path);
for(i = 0; i < list.length; i++) {
open(list[i]);
...//your operations
这里通过for循环,将指定文件夹中的图片逐一打开,然后进行一样的处理。但如果这个文件夹里不仅仅有图片,还有其他文件夹,而且这些文件夹中还有图片呢?
这时利用for循环就会报错,因为open()不能直接打开文件夹。我们当然可以加一个条件结构if,来判断每个文件是图片还是文件夹,但我们依旧无法对文件夹中的文件夹进行操作。
这就要用到递归(Recursion)的思想了。递归,看起来很高大上,但其实我们早就接触过这一概念,举个例子:
从前有座山,山里有座庙,庙里有个和尚,和尚在讲故事,这个故事是:
从前有座山,山里有座庙,庙里有个和尚,和尚在讲故事,这个故事是:
从前有座山...
用表情包就更好说明了:
把这里“大哥”换成函数,“抽”换成调用,其实就表达出了递归的核心思想:
递归函数可以自己调用自己。
递归函数本质就是一个封装了的函数,参考这篇文章:
Treasure琛:ImageJ开发教程——函数封装zhuanlan.zhihu.com举一个最简单的例子,利用递归Macro算阶乘(Factorial):
function f(n) {
if (n<=1)
{
return 1;
}
return n*f(n-1);
}
从这里可以看出递归的两个要素:
1、找到函数自身的关系式。这个例子中,即f(n)=n*f(n-1);
2、递归的终止条件。这个例子中,当n<=1的时候,递归终止。
具体过程可以分解为如下的过程:
f(6)
=> 6 * f(5)
=> 6 * (5 * f(4))
=> 6 * (5 * (4 * f(3)))
=> 6 * (5 * (4 * (3 * f(2))))
=> 6 * (5 * (4 * (3 * (2 * f(1)))))
=> 6 * (5 * (4 * (3 * (2 * 1))))
=> 720
回到文章一开始提到的问题:如果一个文件夹中不仅有图片,而且有文件夹,且该文件夹中还有图片及文件夹...怎样一键处理其中的所有图片?
这篇文章会介绍一个Macro,利用递归的思想解决这一问题。
先上代码:
结果如下,一键将文件夹中的所有图片(包括子文件夹):
一定注意!!!如果不在saveAs()中加入"_after",这个代码就 可以直接覆盖原始数据。
不可恢复!!!三思而后行。
代码解析
代码可以分为三大部分:
一、主体部分(3-5行)
path = getDirectory("Choose Source Directory ");
setBatchMode(true);//Prevent images from showing up to speed up
process_all(path);
首先获得getDirectory函数,获得文件路径path。
setBatchMode(true)相当于批处理模式,不会显示处理过程,从而加快处理过程。
process_all()函数是后面定义的递归函数,需要将path路径输入这个函数。
二、递归函数定义(7-17行)
function process_all(path) {//Recursion function
list = getFileList(path);
for (i=0; i<list.length; i++) {
if (endsWith(list[i], "/")){//It's a folder.
process_all(path+list[i]);}//Recurse to enter the subfolder
else {//It's an image.
dir = path + list[i];
print(dir);//Show processed images
processimage(dir);}
}
}
这里定义了一个process_all(path)函数,需要一个路径path的输入。
1、利用getFileList(path)这个函数,需要获得这个路径下所有的文件列表。
2、然后进入for循环,遍历这个文件列表
3、通过if/else条件语句,判断这个文件是不是文件夹,如果是文件夹,会以"/"为结尾,endsWith()函数会返回TRUE。
接下来就是递归的思想,如果这个文件是一个文件夹,则需要进入这个文件夹,再进行一次同样的判断和处理,所以就可以直接调用自己。
这里就找到了递归的第一个要素:找到函数自身的关系式(这个关系式不一定是数学关系式)
但这时的输入函数的路径就有变化了,path输入需要加上list[i],表示文件夹的路径:process_all(path+list[i])
4、else语句在这里充当了递归的第二个要素:递归的终止条件。
如果这个文件不是一个文件夹,则应该是一张图片(当然还可以再加一个判断,其结尾是否是".tif"),可以然后直接调用processimage()函数。
三、图像处理函数的定义(18-25行)
function processimage(path) {//Process function
open(path);
title = getTitle();
run("8-bit");
run("Invert");
saveAs("Tiff", path + "_after");//Prevent image overwrite
close(title);
}
这里定义了一个processimage(path)函数,需要一个路径path的输入。
这里实现的是将图片转为8-bit、反转并保持到各自文件夹,可以根据自己的需求进行更改。
但一定注意saveAs(),如果直接是saveAs("Tiff", path),保存文件会直接将原始图片覆盖掉,而且是不可逆的,三思而后行!!!
这篇文章的代码可以在GitHub下载到:
GitHub-ImageJ Tutorialgithub.com如果对于ImageJ使用有什么问题可以私信我,或者给我发邮件:zhaoyc9@163.com
更多教程可以关注我的专栏:
ImageJ实用教程zhuanlan.zhihu.com希望对大家有帮助~