使用 ImageMagick 和 Perl 进行图像处理

了解如何使用 PerlMagick 模块从命令行组织和处理多个图像。
346 位读者喜欢这篇文章。
30 years on, Perl and its community continue to thrive

Internet Archive Book Images。由 Opensource.com 修改。CC BY-SA 4.0

PerlMagick 是一个 Perl 模块,允许用户使用各种 ImageMagick 命令创建脚本。大多数用户可能会为了批处理而这样做,甚至是一些串行批处理。在本文中,我将展示我在担任神经科医生期间为了自己的目的而做的一个项目。

如今,神经内科的许多实践都涉及查看各种大脑图像。医院和医生办公室都已设置为直接连接到成像软件,使从业人员可以在线查看这些图像。一项 MRI 研究可能包括数百张图像,因为头部会被检查和重新检查,改变各种参数以突出显示大脑中不同的化学成分,或显示存在运动的区域,例如血管中的血液,与没有运动的区域相比。

这些图像可以保存到 CD 中,我(或患者)可能会将其带到我的办公室以便稍后查看。我无法访问可能进行扫描的所有地方,因此这至关重要。

医学图像以 DICOM 格式保存,这是一种所有 X 射线和扫描设备制造商(如 CT 或 MRI 扫描仪)使用的专有格式。每张光盘还包含读取这些图像的软件,正如您可能期望的那样,该软件仅与 Windows 兼容。如果您单击文件,Linux 本身将不知道如何打开它,但是
ImageMagick 知道,因此当弹出对话框询问使用什么打开文件时,您可以直接输入 display 并查看您的图像。我很快发现我需要向命令添加 -normalize,因为这些灰度图像的比例非常大,即使所有像素可能都处于比例的低端。下面的示例显示了应用 -normalize 之前和之后的同一图像。

Dicom image before and after normalize

同一图像,应用“normalize”命令之前和之后

您可以 cd 到一系列图像所在的目录,在命令行中键入 display -normalize *,然后逐个查看这些图像,但这并不是我们查看扫描的方式。您需要能够一次看到多张图像,因为它们实际上是头部和大脑的成像切片,相隔几毫米。这将使您更好地了解哪些大脑结构受到某些异常的影响,并且您将能够更好地确定异常是什么——是正常大脑发生了改变,还是某些非大脑组织(如肿瘤)?一次查看多张图像是为什么在图像查看数字化之前,扫描件会打印在大型胶片上,以便在灯箱下查看。

我想处理图像的一个原因是,为了在我进行讲座时说明诊断或治疗挑战的讨论中的一些异常成像。当您想展示来自不同患者或来自不同时间点的扫描示例时,使用 CD 甚至是在线软件都很耗时。

在之前的文章中,我已经展示了如何在命令行上轻松地处理图像和蒙太奇。但是在这里,我们讨论的是 CD 上多个目录之间分割的数百张图像。无论如何,这个项目的开始涉及手动操作和评估目录结构。

在这个例子中,实际的图像数据都位于 CD 上名为 dicom 的目录下。在该目录下是一个编号为 5771 的目录,在 5771 下面是一系列子目录:5772、5773,一直到 57717。在每个子目录内部都是实际的图像文件,并且每个目录对应于扫描仪在制作图像时的特定设置序列。在过去的案例中,我见过后缀为 .dcm 的图像文件,但在此 CD 上,它们都只是数字。至少所有图像都在目录结构的同一级别。

开始使用 Perl

Perl 需要做的第一件事是找到所有目录。我决定将这张特定的 CD 硬编码到脚本中

$initdir = "/run/media/gregp/DCS/dicom/5771";

opendir (DIR, $initdir) || die "Do you have cdrom mounted?";

@dicomdir = grep { !/^\./} readdir(DIR);

@dicomdir = grep { !/[a-zA-Z]/ } @dicomdir;

closedir (DIR);

我们打开这个顶层目录,然后将子目录加载到数组 @dicomdir 中,筛选掉 dot 文件和任何带有字母的文件。接下来,我们从每个目录中的文件名构建一个数组

foreach $dicomdir (@dicomdir) {

    opendir (DIR, "$initdir/$dicomdir") || die "no can do";

    @files = grep { !/^\./} readdir(DIR);

    $number = scalar(@files);

    if ($number > 80) {

        print "\nSorry, there were too many files in $dicomdir to process\n";

    }

在我之前关于蒙太奇的文章中,我解释了如果您的蒙太奇太大,您的计算机可能会崩溃,因此在这里我设置了 80 张图像的截止值——如果一个目录中有超过 80 张图像,它将被跳过。

一旦我们克服了这个障碍,我们就可以开始 ImageMagick 的工作了

else {

mkdir "$workdir/$dicomdir";

foreach $file (@files) {

@args = ("cp", "$initdir/$dicomdir/$file", "$workdir/$dicomdir/$file");

system @args;

}

chdir "$workdir/$dicomdir";

$images=Image::Magick->new();

$x=$images->ReadImage("$workdir/$dicomdir/*");

$images->Normalize();

$montage=$images->Montage(geometry=>'300x300',tile=>'4x',label=>'%f',title=>$dicomdir);

$montage->Write("\.\./$dicomdir\.jpg");

   }

我决定首先将所有文件复制到我的硬盘驱动器,然后在那里进行图像处理;因此,每个文件都被复制到一个以 CD 上相同的名称创建的目录中。请记住,我们正在逐个循环遍历包含图像的目录。完成复制后,我将工作目录更改为硬盘驱动器位置,在那里我们可以开始看到 PerlMagick 的面向对象术语。当我第一次编写这个时,我尝试从 DICOM 图像创建 JPG 蒙太奇,认为我可以在查看蒙太奇时进行规范化,但事实证明这效果不佳。因此,我做的第一件事是对每个图像运行 Normalize() 操作,然后继续执行蒙太奇命令,这类似于您在命令行中使用的元素。还值得指出的是,当我将蒙太奇写入硬盘驱动器时,我将其放在图像目录的上方一级——这些反斜杠是为了让 Perl 正确获取所有点。这样,所有蒙太奇都位于一个目录中。

最后一步是清除工作数组并关闭此 CD 目录,因为我们将移动到 @dicomdir 中的下一个目录。

$images=();

closedir(DIR);

@files = ()

}

现在是结果。由于所有这些蒙太奇都处于同一级别,我可以简单地在命令行中键入

display *.jpg

这是一个蒙太奇中有趣的部分

 

cerebellar diffusion images

扩散加权图像。较浅的区域显示正常流体运动比大脑其他部分慢的区域。

这显示了该系列 30 张图像中的 8 张。这些被称为扩散加权图像,这意味着大脑中较浅的区域显示细胞内外的正常流体运动比大脑其他部分慢的区域。该扫描是在右侧小脑半球(在图像的左侧看到;这就是扫描图像的制作方式)中风的患者身上进行的。我的评估是,这些图像的质量与 CD 上的图像质量相同。

这些蒙太奇的另一个优点是它们消除了隐私问题。每个 DICOM 图像文件都包含设施和患者信息,但是通过将每个文件处理为 JPG,此数据将不存在,因此我可以自由地共享和保存图像。一旦我确保一切都正常,我就可以删除硬盘驱动器上的原始图像副本。

这是完整的脚本

#!/usr/bin/perl -w

use Image::Magick;

my($images, $x);

print "Enter the name of the folder for the first case:\t";

chomp ($folder =<STDIN>);

$workdir = "/home/gregp/dicom/$folder";

mkdir $workdir || die "Could not make $folder: $!";

$initdir = "/run/media/gregp/DCS/dicom/5771";

opendir (DIR, $initdir) || die "Do you have cdrom mounted?";

@dicomdir = grep { !/^\./} readdir(DIR);

@dicomdir = grep { !/[a-zA-Z]/ } @dicomdir;

closedir (DIR);

foreach $dicomdir(@dicomdir) {

print "$dicomdir\t";

}

foreach $dicomdir (@dicomdir) {

opendir (DIR, "$initdir/$dicomdir") || die "no can do";

@files = grep { !/^\./} readdir(DIR);

$number = scalar(@files);

if ($number > 80) {

print "\nSorry, there were too many files in $dicomdir to process\n";

}

else {

mkdir "$workdir/$dicomdir";

foreach $file (@files) {

@args = ("cp", "$initdir/$dicomdir/$file", "$workdir/$dicomdir/$file");

system @args;

}

chdir "$workdir/$dicomdir";

$images=Image::Magick->new();

$x=$images->ReadImage("$workdir/$dicomdir/*");

$images->Normalize();

$montage=$images->Montage(geometry=>'300x300',tile=>'4x',label=>'%f',title=>$dicomdir);

$montage->Write("\.\./$dicomdir\.jpg");

}

$images=();

closedir(DIR);

@files = ()

}

我之前没有展示的一个元素是,最初这会要求输入蒙太奇将要放入的目录的名称;另一个是显示要检查的目录的消息。

Greg Pittman
Greg 是肯塔基州路易斯维尔的一位退休神经科医生,他对计算机和编程有着长期的兴趣,从 1960 年代的 Fortran IV 开始。当 Linux 和开源软件出现时,它激发了学习更多知识的承诺,并最终做出了贡献。他是 Scribus 团队的成员。

2 条评论

这是一篇有趣的文章,但是由于格式问题,代码很难阅读。我假设代码双倍行距且没有缩进并非有意为之?

在一行上显示 4 个纵向格式可以
但是如果是横向格式,它们会在该行上被挤压在一起

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.