原始 MediaWiki 页面

我知道编辑这个网站吗?
the ImageJ2 platform本页介绍与 the ImageJ2 platform 相关的内容。点击徽标查看详情。

Clojure 脚本

自迁移出 MediaWiki 以来,本页内容尚未经过审查。如果您愿意帮忙,请查看帮助指南

Clojure is a dialect of the Lisp programming language. Clojure is a general-purpose programming language with an emphasis on functional programming.

ImageJ 的 Clojure 教程

查看clojure website,特别是关于Java interoperability的章节。

Clojure“不是”脚本语言:Clojure 直接编译为 JVM 字节码,以本机速度运行。因此,人们必须将 Clojure 视为 Java 语言的真正替代产品,但增加了表现力、灵活性且强大。

另请参阅:

在斐济使用 Clojure

转到PluginsScriptingRefresh Clojure Scripts。该提示接受任何 clojure 代码。另请参阅Script Editor

有关键绑定以及如何使用解释器的详细信息,请参阅Scripting Help⇧ Shift + R将添加所有必要的结束字符串。

一个最小、完整的 Clojure 示例:

(import '(ij IJ))
(def gold (IJ/openImage "https://imagej.net/ij/images/AuPbSn40.jpg"))
(.show gold)

要创建脚本,只需将它们另存为 .clj 文本文件(名称中标有下划线)到 plugins 文件夹的任何文件夹或子文件夹中,然后运行 ​​PluginsScriptingClojure Interpreter 来更新菜单(这也会在启动时自动完成)。

要编辑脚本,只需使用您喜欢的文本编辑器进行编辑并保存即可。

要执行脚本,请执行以下任一操作:

  • 从插件菜单中选择它。
  • 输入“l”(L),开始输入其名称,按上级箭头,然后返回以执行。
  • 如果这是最后执行的命令,只需键入 ⌃ Ctrl + )(“处理 - 重复命令”的快捷方式)。

该脚本始终直接从源文件读取,因此不需要更新菜单(除非其文件名更改)。

方便的 Clojure 和 Funimage

  • FunimageImageJ2中提供了一个方便的Clojure编码库。减少了对类型提示的大部分需求,以及处理更复杂的数据结构(例如第ImgLib2中的数据结构)所涉及的一些负担。

参见list of update sites 相关设置 Funimage 更新站点的信息。

从命令行运行 Clojure 文件

ImageJ2可以直接执行任何clojure文件:

./ImageJ-linux64 plugins/Examples/blend_two_images.clj

该文件将使用 ImageJ2 设置的完整类路径运行,其中包括 jars/plugins/ 文件夹中的所有 jar 等。

语言基础知识

  • 一个“;”定义注释的开始,就像Java中的“//”一样。
  • 函数定义在[]内声明参数。
  • 局部变量用let声明,全局变量用def声明。
  • 函数由defn定义,并且全局可见。因此,在let语句中声明的函数可以访问其中声明的变量。该方法可以实现闭包。

导入类

要从 Clojure 中引用 Java 类,您需要导入它们。

与原始 ImageJ 不同,ImageJ2(因此也包括 Fiji)不会自动导入任何类。因此,为 ImageJ 编写的脚本如果不添加正确的导入,将无法在 ImageJ2 中运行。原因是自动导入功能并不安全。如果两个同名类位于两个不同的包中怎么办?或者引入了一个新类,使原本唯一的名称产生歧义怎么办?突然间,所有引用原始类的脚本都不再工作。简而言之:自动导入存在危险的精确性问题。


You can specify imports in Clojure in a few ways:

; A single import.
(import java.util.Date)

; Use it!
(def *now* (Date.))
(str *now*)

; Multiple imports at once.
(import '(java.util Date Calendar)
        '(java.net URI ServerSocket)
        java.sql.DriverManager)

; Import multiple classes in a namespace.
(ns foo.bar
  (:import (java.util Date
                      Calendar)
           (java.util.logging Logger
                              Level)))

在 java 对象上调用方法和变量

有两种方法,第一种是第二语法糖。下面,imp是一个指向ImagePlus的指针:

; java-ish way:
(. imp (getProcessor))

; shorter java-ish way:
(. imp getProcessor)

; lisp-ish way:
(.getProcessor imp)

要在方法调用返回的对象上调用方法,有一种简化的方法:

; double way:
(. (. imp (getProcessor)) (getPixels))

; simplified double way:
(.. imp (getProcessor) (getPixels))

; super simplified (less parenthesis than java!)
(.. imp getProcessor getPixels)

; or lisp-ish way:
(.getPixels (.getProcessor imp))

任意数量的集群方法调用,无论是静态方法还是实例:

; Concatenated call to static and instance methods
(.. WindowManager getCurrentImage getProcessor getPixels)

要调用变量或“字段”,只需像方法调用一样进行,但不带引号:

(. imp changes)

牙齿不清的:

(.changes imp)

为了增强吸引力,请在适当的时候使用import。导入在整个当前命名空间中保持可见:

(import '(java.awt Color Rectangle)
        '(ij.plugin.filter PlugInFilter))

(new Rectangle 0 0 500 500)
; It's the same as:
(Rectangle. 0 0 500 500)

; A static field, call like a namespace:
PlugInFilter/DOES_ALL

选择最符合您心理计划的内容。

调用静态字段和方法:命名空间语法

要调用static字段或方法,请使用命名空间语法:

(println "does all: " ij.plugin.filter.PlugInFilter/DOES_ALL)

(ij.IJ/log "Some logged text")

在上面,请注意如何使用类名而不是指针来调用静态字段和方法。静态字段和方法只是存在声明它们类的命名空间中的变量和函数。因此,Clojure 的命名空间语法比 java 代码更有意义,java 代码没有这种区别,并且允许大量的不一致(java 允许使用指向此类静态方法和字段的类实例的指针来调用静态方法和字段)。

定义变量:获取当前图像

作为在 let 语句中声明的局部变量imp

(let [imp (ij.WindowManager/getCurrentImage)]
    ; print its name
    (println (.getTitle imp)))

; Variable imp NOT visible from outside let statement:
(println (.getTitle imp))
---> ERROR

作为从整个命名空间可见的通用信号:

(def *imp* (ij.WindowManager/getCurrentImage))

(println (.getTitle *imp*))

基本上,在 Lisp 中,全局变量的名称中标有星号。

let语句允许您声明任意数量的成对变量名称/值,甚至可以按顺序相互引用:

(let [imp (ij.WindowManager/getCurrentImage)
      ip (.getProcessor imp)
      pix (.getPixels ip)
      pix2 (.getPixels (.getProcessor imp))]
    ; do some processing ...
    (println (str "number of pixels: " (count pix))))

任何数量的let语句都可以一起使用:

(let [imp (ij.WindowManager/getCurrentImage)]
    ; do whatever processing here
    (let [ip (.getProcessor imp]
        ; print first pixel
    (println (str (.getPixel ip 0 0))))))

创建对象:调用构造函数

通过添加点“.”来调用构造函数。类的名称,后面跟参数。下面,我们创建一个ImageProcessor,然后用它创建一个ImagePlus,最后我们打印ImagePlus,它调用它的toString()(就像在java中一样):

(let [ip (ij.process.ByteProcessor. 400 400)
      imp (ij.ImagePlus. "my image" ip)]
  (println imp))

另一种语法是使用类似 java 的 new 关键字,但它不必要冗长:

(let [ip (new ij.process.ByteProcessor 400 400)
      imp (new ij.ImagePlus "my image" ip)]
  (println imp))

定义一个闭包

下面在局部变量rand的范围内声明了一个函数,其中包含 java.util.Random 的实例。对函数rand-double的所有调用都将使用 Seed 为 69997 的相同随机数生成器实例。

然后dotimes循环将打印10个不同的伪随机数。如果rand每次都是种子为69997的新随机数,则所有10个数字将有几个。

您可以将闭包内的函数视为使用静态变量(在 Java 中)的静态函数,但不仅如此,因为该函数将能够访问全局命名空间以及声明 let 的任何其他本地命名空间中的参数。例如,另一个let,甚至另一个defn

(let [rand (java.util.Random. 69997)]
    (defn rand-double []
        (.nextDouble rand)))

    (dotimes [i 10]
        (println (rand-double)))

请注意上面的点“.”在 Random 之后,这表明我们正在调用构造函数(使用单个参数 69997,即要使用的随机生成种子器)。或者可以,使用类似 java 的语法: (new java.util.Random 69997) – 请注意现在缺少点。

处理图像

ImageJ 图像内部结构:ImagePlus、ImageProcessor、ImageStack

ImageJ 具有三个基本对象:

  • ImagePlus,它包装 ImageProcessor 并包含指向 ROI(感兴趣的区域)和可能显示图像的 ImageWindow 的属性和指针。
  • ImageProcessor,它是一个抽象类,可以对像素进行高级操作和访问。它的每个子类都包装了不同类型的数据类型:
    • ByteProcessor - byte[]
    • ShortProcessor - short[]
    • FloatProcessor - float[]
    • ColorProcessor - int[] (byte-packed ARGB, but Alpha channel is ignored)
  • 不幸的是,ImageStack不是包含ImageProcessor,的存储器,而是包含Object[]Object[],其中包含等长byte[]float[]等的同类列表。

有关详细文档,请参阅Anatomy of an ImageJ image ImageJ编程基础教程。

###命名图像标志的约定

依据当前情况,指标命名为:

  • imp 表示 ImagePlus
  • ip表示图像处理器
  • stack 表示 ImageStack

###创建一个新图像

从头开始:

(import '(ij ImagePlus)
        '(ij.process ByteProcessor))

(let [imp (ImagePlus. "A new image" (ByteProcessor. 400 400))]
  (.show imp))

来自文件:

(let [imp (IJ/openImage "/path/to/an/image.tif")]
  (.show imp))

创建与现有相同图像类型的图像

; The original
(def imp-1 (ImagePlus. "The source image" (FloatProcessor. 512 512)))

; The new empty image, of the same type as the old but larger
(def imp-2 (ImagePlus. "The larger image of the same type"
                       (.. imp-1 getProcessor (createProcessor 768 768))))

请注意上面的逗号 (createProcessor 768 768),它指定这些数字是哪个方法的参数。

调整图像大小

这个想法是获取 ImageProcessor,复制它并调整它的大小。调整大小会返回一个相同类型的新 ImageProcessor:

(def imp-1 (IJ/openImage "/path/to/image1.tif"))

(def imp-2 (ImagePlus. "A new larger one" (.. imp-1 getProcessor (createProcessor 1024 1024))))

; Copy one into the other at top-left (hence 0,0 insert point):
(doto (.getProcessor imp-2)
  (.insert (.getProcessor imp-1) 0 0))

另一种方法是简单地复制imp-1的处理器,然后将其放大:

(def imp-3 (ImagePlus. "A copy with extra empty space"
                       (.. imp-1 getProcessor duplicate (resize 768 768)))

调整ImageStack的大小

这个比较难,因为 ImageStack 只是 Object[] 像素阵列列表的包装。 ImageJ 通过 CanvasResizer 类提供了中级调整大小方法:

(import '(ij.plugin CanvasResizer)
        '(ij IJ ImagePlus))

; Grab the image in the currently active ImageWindow:
(def imp-1 (IJ/getImage))

; function to resize images:
(defn resize-image
  "Takes an ImagePlus as argument and returns a new ImagePlus
   but resized to width,height, and with the contents copied
   starting from xoff,yoff"
  [imp w h xoff yoff]
  (let [resizer (CanvasResizer.)
        stack (.getStack imp)
        imp-2 (ImagePlus. (.getTitle imp)
                (if stack
                  (.expandStack resizer stack w h xoff yoff)
                  (.expandProcessor resizer w h xoff yoff)))]
    imp-2))

(def imp-2 (resize-image imp-1 1024 1024 0 0))

(.show imp-2)

请注意,上述函数resize-image适用于堆栈和非堆栈图像。

当然,没有什么可以阻止您循环遍历堆栈长度,为每个切片调用一个新的 ImageProcessor,调整其大小,组成一个新的 ImageStack 以及一个新的 ImagePlus。

; Grab the image in the currently active ImageWindow:
(def imp-1 (IJ/getImage))

(defn resize-stack
  "Resize an ImageStack to new widht,height
   and copy its contents starting at xoff,yoff coordinate."
  [stack w h xoff yoff]
  (let [new-stack (ImageStack. w h nil)]
    (doseq [i (range 0 (.getSize stack))]
      (let [ip (.getProcessor stack (+ i 1))
            #^ImageProcessor ip2 (.createProcessor ip w h)]
        (.insert ip2 ip xoff yoff)
        (.addSlice new-stack (str i) ip2)))
    new-stack))

上面,请注意堆栈是从 1 开始的,而不是从 0 开始的!

另外,我们必须声明 ip2 的类型,因为 clojure 无法在 ImageProcessor.addSlice(String,ImageProcessor) 和 ImageProcessor.addSlice(String,Object) 之间做出决定。您必须为 clojure 做出这样的选择。

请注意,每次在 ImageStack 上调用 getProcessor 时,它都会以一种非常昂贵的方式返回一个新的 ImageProcessor 实例,即通过在像素队列上调用一系列 instanceof 来确定应创建哪种类型的 ImageProcessor 子类。

使用 ROI 调整图像或 ImageStack 的大小

ROI(又名感兴趣的区域或选择)具有由最小化定义的边界。

核心思想是为ImageProcessor设置ROI并调用crop来获取它的新子副本。

(def imp (IJ/getImage))

(def imp-cropped (ImagePlus. "Cropped"
                             (let [ip (.getProcessor imp)]
                               (.setRoi ip (Roi. 10 10 200 200))
                               (.crop ip))))

(.show imp-cropped)

要处理任何ImagePlus(具有单个切片或包含ImageStack,即多个切片),请参见此函数:

(假设 ROI 完全包含在图像中;否则对于堆栈,将发送异常,说明尺寸不匹配。)

(import '(ij.gui Roi)
        '(ij ImagePlus)
        '(ij.process ImageProcessor))

(def imp (IJ/getImage))

(defn crop-image
  "Crop an image by the bounds of a ROI,
   returning a new ImagePlus with the result."
  [imp roi]
  (let [crop-processor (fn [ip roi]
                         (.setRoi ip roi)
                         (.crop ip))
        stack (.getStack imp)]
    ; Return a new ImagePlus with a new cropped ImageProcessor
    ; or a new cropped ImageStack:
    (ImagePlus. (.getTitle imp)
      (if stack
        (let [box (.getBounds roi)
              new-stack (ImageStack. (.width box) (.height box) nil)]
          (doseq [i (range (.getSize stack))]
            (.addSlice new-stack (.getSliceLabel stack (+ i 1))
                       #^ImageProcessor (crop-processor
                                          (.getProcessor stack (+ i 1))
                                          roi)))
          new-stack)
        ; Else single slice image:
        (crop-processor (.getProcessor imp) roi)))))

(def imp-cropped (crop-image imp (Roi. 100 100 300 300))

(.show imp-cropped)

以上适用于单个图像和堆栈。

使用 ImgLib 图像操作

使用Imglib,像素存储在原语的本机读写中,例如 int、float、double 等(或其他更有趣的形式,例如Shape)。此类像素通过 JIT 能够完全删除中间的代理对象进行访问。

在 Clojure 中,可以通过多种方式访问像素。这里我们列出了访问器 Type 对象集合访问像素的一些示例。

将每个像素乘以 0.5

每个值就地乘以 0.5。 ImgLib/wrap 是一个直接访问CPU的薄包装器。因此原始图像将被改变。

; ASSUMES the current image is 32-bit
(ns test.imglib
 (:import [mpicbg.imglib.image Image]
          [script.imglib ImgLib]
          [mpicbg.imglib.type.numeric.real FloatType]
          [ij IJ]))

(set! *warn-on-reflection* true)

(let [^Image img (ImgLib/wrap (IJ/getImage))
     a (float 0.5)]
 (doseq [^FloatType t img]
   (.mul t a)))

在更实用的风格中,下面我们创建一个与包装图像具有相同尺寸的图像,并将其像素值设置为原始图像的像素值乘以0.5:

(ns test.imglib
 (:import [mpicbg.imglib.image Image]
          [mpicbg.imglib.cursor Cursor]
          [script.imglib ImgLib]
          [mpicbg.imglib.type.numeric NumericType]
          [ij IJ]))

(set! *warn-on-reflection* true)

(let [^Image img (ImgLib/wrap (IJ/getImage))
      a (float 0.5)
      ^Image copy (.createNewImage img "copy")]
  (with-open [^Cursor c1 (.createCursor img)
              ^Cursor c2 (.createCursor copy)]
    (loop []
      (if (.hasNext c1)
        (do
          (.fwd c1)
          (.fwd c2)
          (let [^NumericType t1 (.getType c1)
                ^NumericType t2 (.getType c2)]
            (.set t2 t1)
            (.mul t2 a))
          (recur)))))
  (.. copy getDisplay setMinMax)
  (.show (ImgLib/wrap copy)))

然而,上面的内容冗长得令人难以忍受。对图像的高级访问可能会造成数学损伤,从而消耗任何性能:

(ns test.imglib
 (:import [mpicbg.imglib.image Image]
          [mpicbg.imglib.cursor Cursor]
          [script.imglib ImgLib]
          [script.imglib.math Compute Multiply]
          [ij IJ]))

(set! *warn-on-reflection* true)

(let [^Image img (ImgLib/wrap (IJ/getImage))
      ^Image copy (Compute/inFloats (Multiply. img 0.5))]
    (.. copy getDisplay setMinMax)
    (.show (ImgLib/wrap copy)))

另外,Compute/inFloats 方法是作为硬件运行的,处理器数量与机器的内核数量相同。如果您想要硬件运行操作,则需要所需的线程数参数添加到inFloats

java.lang.Math 中推出的所有数学攻击都有一个相应的构造函数,用于在 Compute/inFloats 中执行。请参阅 script.imglib.math package 的文档。

标准化图像

假设(IJ/getImage)返回32位浮点图像。如果不是这种情况,请先将图像转换为浮动图像。

下面的示例创建一个新的结果图像。原始图像未检索。通过使用 imglib 的高级脚本库和 Compute/inFloats 方法,可以以最小的表面实现最佳性能(例如使用字体手动编码或更好)。

(ns test.imglib
 (:import [mpicbg.imglib.image Image]
          [script.imglib ImgLib]
          [script.imglib.math Compute Subtract Divide]
          [ij IJ]))


(let [^Image img (ImgLib/wrap (IJ/getImage))
     size (.size img)
     mean (reduce
            #(+ %1 (/ (.getRealFloat %2) size))
            (float 0)
            img)
     variance (/ (reduce
                   #(+ %1 (Math/pow (- (.getRealFloat %2) mean) (float 2)))
                   (float 0)
                   img)
                 size)
     std-dev (Math/sqrt variance)
     ^Image normalized (Compute/inFloats (Divide. (Subtract. img
mean) std-dev))]
 (.. normalized getDisplay setMinMax)
 (.show (ImgLib/wrap normalized)))

有一种更好的方法来计算数字集合的均值和方差,该方法只需要遍历该集合一次。Clojure 自然有助于其非常简洁的解构和自动提升数字类型溢出。

(ns test.imglib
 (:import [mpicbg.imglib.image Image]
          [mpicbg.imglib.type.numeric RealType]
          [script.imglib ImgLib]
          [script.imglib.math Compute Subtract Divide]
          [ij IJ]))

(set! *warn-on-reflection* true)

(let [^Image img (ImgLib/wrap (IJ/getImage))
      size (.size img)
      [xs x2s] (reduce (fn [accum ^RealType t]
                         (let [xi (.getRealFloat t)]
                           [(+ (accum 0) xi)
                            (+ (accum 1) (* xi xi))]))
                       [0 0]
                       img)
      mean (/ xs size)
      variance (- (/ x2s size) (* mean mean))
      std-dev (Math/sqrt variance)
      ^Image normalized (Compute/inFloats (Divide. (Subtract. img mean) std-dev))]
  (.. normalized getDisplay setMinMax)
  (.show (ImgLib/wrap normalized)))

(代码改编自 Common Lisp 版本,由 Patrick Stein.)

循环带宽

例如,要查找顶部和顶部:

; Obtain the pixels array from the current image
(let [imp (ij.WindowManager/getCurrentImage)
      pixels (.. imp getProcessor getPixels)
      min (apply min pixels)
      max (apply max pixels)]
    (println (str "min: " min ", max: " max)))

上面的代码没有显式的循环像素:它只是函数调用队列。

要逐个循环像素,请使用以下任一方法:

(let [imp (ij.WindowManager/getCurrentImage)
      pixels (.. imp getProcessor getPixels)]

      ; First loop with "dotimes"
      (dotimes [i (count pixels)]
          (println (aget pixels i)))

      ; Second loop: with "loop -- recur"
      (loop [i 0
             len (count pixels)]
         (if (< i len)
            (do
               (println (str i ": " (aget pixels i)))
               (recur (inc i)
                      len)))))

请注意,上面的loop -- recur构造本质上是一个let声明,并通过第二次调用(recur)将变量重置为其他值。在本例中,为注意阵列中的下一个索引。请len是如何简单地再次重复地赋予相同的值,只是避免在每次重复时调用(count pixels)

当然,还有更简单的方法来循环像素队列。例如,要获得所有像素的工具,我们可以使用函数reduce,它获取列表的前两个元素,对它们应用函数,然后将函数评估结果和下一个元素,等等:

(let [fp (.getProcessor (ij.IJ/getImage))
      pix (.getPixels fp)]
   (if (instance? ij.process.FloatProcessor fp)
      (println "Average pixel intensity" (/ (reduce + pix) (count pix)))
      (println "Not a 32-bit image")))

在上面,请注意,也可以使用 apply+ 调度内存的所有元素,得到相同的结果:

      (println "Average pixel intensity" (/ (apply + pix) (count pix)))

为了对8位图像中的所有像素求和,需要首先对所有字节进行位侵犯,然后将所有字节转换为255,这样它们就变成整数并可以求和。但我们当然不应该bit-and这个总和!为了解决这个问题,reduce接受第一个值(在本例中清楚):

(let [bp (.getProcessor (ij.IJ/getImage))
      pix (.getPixels bp)]
  (if (instance? ij.process.ByteProcessor bp)
    (println "Average intensity: " (float (/ (reduce (fn [x1 x2] (+ x1 (bit-and x2 255))) 0 pix) (count pix))))
    (println "Not an 8-bit image")))

它甚至可以使用局部变量来完成,但它很丑且贫不可取(为什么在需要时创建它)?请注意,我们需要创建局部变量“sum”,因为let声明的变量是不可可变的。

(let [bp (.getProcessor (ij.IJ/getImage))
      pix (.getPixels bp)]
  (if (instance? ij.process.ByteProcessor bp)
    (with-local-vars (sum 0)
      (doseq [pixel pix]
        (var-set sum (+ (var-get sum) (bit-and pixel 255))))
      (println (float (/ (var-get sum) (count pix)))))
    (println "Not an 8-bit image")))

从菜单执行命令

任何ImageJ菜单命令都可以在活动图像上运行:

(ij.IJ/doCommand "Add Noise")

请注意,上面启动了一个新线程并分叉。为了可靠的控制,请尝试运行方法,该方法将等待插件完成执行。

(ij.IJ/run "Add Noise")

为了获得更可靠的控制,请直接在指定的图像上运行命令,而不是可能更改的当前图像:

(let [imp (ij.IJ/getImage)]
  (ij.IJ/run imp "Subtract..." "value=25"))

要了解任何命令可以接受哪些参数,请打开插件 - 宏 - 宏记录器并运行手动命令。

创建并使用 Clojure 脚本作为 ImageJ 插件

接下来创建一个包含脚本的文本文件,将其放置在插件菜单或任何文件夹中。然后调用Plugins - Scripting - Refresh Clojure Scripts 出现在菜单上。

如果 Macros/StartupMacros.txt 在 AutoRun 宏中包含对刷新 Clojure 脚本的调用,则所有 Clojure 脚本将在启动时自动显示。

要修改已作为菜单项存在的脚本,只需编辑其文件并从菜单中选择它即可运行。

All scripts and commands from the interpreter will run within the same thread, and within the same clojure context.

使用 java beans 快速方便地访问对象的字段

本质上,这都是关于以简单的方式使用 get 方法。例如:

(let [imp (ij.WindowManager/getCurrentImage)
      b (bean imp)]
   (println (:title b)
            (:width b)
            (:height b)))

最终 Clojure 可能还会添加对 set 方法的支持。

示例

修复曝光过度的图像:将所有曝光过度的像素设置为所需的像素值

问题:Leginon 或 Gatan TEM 相机获取过度曝光的图像,将超出范围的所有像素设置相等。

解决方法:迭代所有像素;如果像素为零,则将其设置为需要的值,例如直方图表示主曲线的顶点(在“亮度和坐标”对话框中按“自动”即可查看它。)

在下面的示例中,使用当前图像和浮点数形式的值 32500 调用 fix函数。另外请注意浮点像素队列的定义(可选),以提高执行速度:

; Assumes a FloatProcessor image
(defn fix [imp max]
(let [#^floats pix (.getPixels (.getProcessor imp))]
  (loop [i (int 0)]
    (if (< i (alength pix))
      (do
        (if (= 0 (aget pix i)) (aset pix i (float max)))
        (recur (inc i)))))))

(let [imp (ij.IJ/getImage)]
  (fix imp (float 32500))
  (.updateAndDraw imp))

为 ImageJ 创建脚本

将 clojure 脚本写入文本文件,并遵循以下约定:

1.在文件名中添加下划线_,扩展名.cljfix_leginon_images.clj

  1. 将其保存在fiji/plugins/文件夹或子文件夹下。

完成后,只需运行 PlugInsScriptingRefresh Clojure Scripts 插件即可。

一旦保存并进入菜单,您就不需要再次为该脚本调用刷新脚本。只需编辑并保存其文本文件,然后从菜单中再次运行即可。接下来打开 ImageJ2 时,该脚本将自动出现在菜单中。

有关更多详细信息,请参阅第§§11§§§,包括如何使用内置动态解释器。

斐济包含的 Clojure 插件示例

ImageJ2 的 Fiji 发行版包括一些 Clojure 示例:

  • blend_two_images.clj:用宏(通过defmacro)说明如何使用图像的各自解释的自动多线程处理图像,例如每个线程一行,对于与CPU的内核相同的多线程。
  • Multithreaded_Image_Processing.clj:说明如何从URL打开两个图像,将灰色图像混合到每个彩色图像的通道中。
  • random_noise_example.clj:说明了 Swing GUI 的用法,以及如何从接口实例化匿名类(通过proxy Clojure 函数)。这个例子取自Clojure website
  • celsius_to_fahrenheit.clj:说明如何在闭包内声明函数(用于内存访问,在本例中是随机数生成器的唯一实例),然后用随机字节值填充 ByteProcessor 图像的所有像素。
  • Dynamic ROI Profiler:说明如何使用 KeyListener GUI,键入以便在与 ImageJ 命令匹配的文本中从红色变为黑色。此示例也位于 Scripting comparisons 下,以及用 Java 编写的相应版本,JythonJavascriptJRuby
  • Command_Launcher_Clojure.clj:说明如何将 MouseMotionListener 和 WindowListener 添加到打开图像的 ImageWindow。写入 ROI(感兴趣的区域),如果是一条线、折线或平整,则相当于沿该线的像素强度。当鼠标移动或编辑图像上的 ROI 时,配置文件就会更新。

附录

定义输出流

默认输出流位于变量*out*中,您可以将其重新定义为任何类型的PrintWriter:

(let [all-out (new java.io.StringWriter)]
  (binding [*out* all-out]
     ; any typed input here
     ; All calls to pr, prn, println will print into all-out
     (println "this and that")
   )
   ; Now show any printed out text in ImageJ's log window:
   (ij.IJ/log (str all-out)))

解构

解构一个指标的内容捕获到多个指标中的捷径。

一个例子:循环映射时,我们获取入口,而不是每个入口的键和值:

(doseq [e {:a 1 :b 2 :c 3}]
  (println e))

印刷:

[:a 1]
[:b 2]
[:c 3]
nil

每个“边界”由具有两个值的表示表示。

现在为了更方便地循环,我们可以通过称为解构将键和值分配给变量(注意[k v]之前的e

(doseq [[k v] {:a 1 :b 2 :c 3}]
  (println k v))

印刷:

:a 1
:b 2
:c 3
nil

更好的是,我们还可以通过使用关键字“:as”来保留整个边界:

(doseq [[k v :as e] {:a 1 :b 2 :c 3}]
  (println k v e))

印刷:

:a 1 [:a 1]
:b 2 [:b 2]
:c 3 [:c 3]
nil

命名空间

  • 列出所有现有的命名空间:
      >>> (all-ns)
      (#<Namespace: xml> #<Namespace: zip> #<Namespace: clojure> #<Namespace: set> #<Namespace: user>)
    
  • 要列出特定命名空间的所有函数和变量,首先按名称获取命名空间对象:
      (ns-map (find-ns 'xml))
    

    请注意上面带引号的字符串“xml”,深圳市将其计算为(不存在的)值。

  • 列出所有命名空间的所有函数和变量:
      (map ns-map (all-ns))
    

    打印所有命名空间中的所有公共函数和变量的更好方法,按字母顺序排序:

    (doseq [name (all-ns)]
    (doseq [[k v] (sort (ns-publics name))]
      (println k v)))
    

    注意上面我们使用解构[k v]采用键的值和 ns-publics 表中每个入口的值。 实际上,由于我们首先对表进行排序,所以 kv 会采用将 sortns-publics 生成的表时返回的集群对排序列表中每个队列的第一个和第二个值。

忘记/删除命名空间中的所有指标

要忘记用户命名空间中的所有变量,请执行以下操作:

(map #(ns-unmap 'user %)
     (keys (ns-interns 'user)))

上面将函数ns-unmap映射到user命名空间中声明的每个变量名(使用#创建lambda function),这与提示命名空间相同。为了获取变量的名称,我们使用ns-interns来检索变量名称与变量内容的映射,并将其中的键提取到列表中。

感谢来自 irc.freenode.net 的 #clojure 的 AWizzArd 提供的提示。

JVM 参数

  • 要获取传递给 JVM 的参数,请参见参数 command-line-args 的内容
      (println *command-line-args*)
    

    ##期待

  • 上市对象的所有方法:
      (defn print-java-methods [obj]
        (doseq [method (seq (.getMethods (if (= (class obj) java.lang.Class)
                                          (identity obj)
                                          (class obj))))]
          (println method)))
    
       ; Inspect an object named imp, perhaps an image
       (print-java-methods imp)
    
       public synchronized boolean ij.ImagePlus.lock()
       public void ij.ImagePlus.setProperty(java.lang.String,java.lang.Object)
       public java.lang.Object ij.ImagePlus.getProperty(java.lang.String)
       ...
    
  • 要初始化构造函数,只需使用 .getConstructors 而不是 .getMethods

(感谢 Craig McDaniel 将上述函数发布到 Clojure 的邮件列表。)

Lambda 函数

###声明

  • 以 lambda 风格动态声明函数,并使用正则表达式作为参数:

例如,声明一个接受2个参数的函数,并返回第一个参数除以10并乘以第二个参数的值:

(let [doer #(* (/ %1 10) %2)]
  (doer 3 2))

当然函数的命名就不用指定,上面只是为了说明。

将函数映射到列表中的所有元素

  • 声明一个无名函数,附有其评估列表的每个元素:

在本例中,将列表中从 0 到 9 的每个值加一:

(let [numbers (range 10)
      add-one (fn [x] (+ x 1))]
  (map add-one numbers))

名称填写声明,以上仅用于说明。上面,我们可以将函数定义为#(+ %1 1):

(map #(+ %1 1) (range 10))

…或者当然使用内部函数inc,正是这样做的:

(map inc (range 4))

请注意,上面的map函数将给定的函数评估列表的每个元素,并返回一个包含结果的列表。

内置文档

使用函数doc查询任何其他函数或变量。例如,列表生成器函数range

(doc range)

-------------------------
clojure/range
([end] [start end] [start end step])
  Returns a lazy seq of nums from start (inclusive) to end
  (exclusive), by step, where start defaults to 0 and step to 1.

在上面,请注意该函数具有三组可能的参数,用逗号表示。

当不知道要搜索什么时,您可以尝试 find-doc,它接受字符串或正则作为表达式参数:

user=> (find-doc "ns-")
-------------------------
clojure.core/ns-aliases
([ns])
  Returns a map of the aliases for the namespace.
-------------------------
clojure.core/ns-imports
([ns])
  Returns a map of the import mappings for the namespace.

... etc.

为您自己的函数定义文档

那么文档从哪里来呢?函数、宏数学方法的每个定义都可以在参数之前采用描述字符串:

(defn area
  "Computes the area of a rectangle."
  [r]
  (* (.width r) (.height r)))

doc函数打印的内容,格式为:

user=> (doc area)
-------------------------
user/area
([r])
  Computes the area of a rectangle.
nil

定义指标的文档

(def
  #^{:doc "The maximum number of connections"}
  max-con 10)

doc 函数打印为:

user=> (doc max-con)
-------------------------
user/max-con
nil
  The maximum number of connections
nil

函数文档的内部设置与上面类似:defn是一个宏,它定义了一个函数,放置了第二个参数作为指向函数体(以及许多内容)的变量的文档其他字符串。

将测试函数添加到变量

我们首先声明变量,然后使用包含测试函数的元数据映射来定义它:

(declare a)
(def
  #^{:test #(if (< a 10) (throw (Exception. "Value under 10!")))}
  a 6)

…,我们通过调用函数test进行测试,而不是对变量a(可能有自己的元数据映射)的值调用函数test,而不是对变量a本身(用#'a引用)调用:

(test #'a)

在本例中,测试结果抛出异常:

java.lang.Exception: Value under 10!

否则,它仅返回 :ok 关键字。

斐波那契数列:指定序列和无限序列

这是一个使用指定序列放入函数一次检查一个或多个序列的美丽示例。

下面,序列fibs被定义为包含所有可能的fibonacci numbers。由于这样的序列是无限的,我们将其声明为lazy序列,仅在需要时创建新元素。

lazy-cat clojure函数通过连接两个序列来创建这样的调用序列:第一个序列是0, 1(它相当于馈送器或初始化序列),第二个序列是对fibs序列本身的两个子集进行map操作的结果:完整和完整的删除第一个元素(因此,rest操作获取所有元素的列表,而不需要首先)。

map损害函数将评估序列的每个元素,或者,当提供两个或多个序列时,评估相应的元素:所有序列中索引0处的元素,所有序列中索引1处的元素等。

为了生成数字的斐波契序列,求和+腐蚀被映射到自身序列fibs中包含的数字以及自身序列fibs减少第一个元素的相应元素,即移位一个

因此,指定序列fibs是一种表示潜在无限序列的抽象方式,其实现包含所有斐波那契数的完整抽象定义。

然后我们只需take调用序列的前10个元素,它们是动态创建的。

(def fibs (lazy-cat [0 1]
                    (map + fibs (rest fibs))))

(take 10 fibs)

哪个输出:

(0 1 1 2 3 5 8 13 21 34)

将调用序列打印到 REPL

当给定一个调用序列时,REPL 将完整地检索它并打印它。

将潜在的无限延迟每个队列打印到 REPL 是您不想做的事情:除了触发元素的计算之外,它还会填充所有内存并引发 OutOfMemoryException。看到元素后你会感到无聊。

一个好的选择只是打印其中的一部分:

  • take:前N个元素。
  • drop:N 之外的所有元素。
  • nth:仅限第n个元素。

对于无限调用序列,drop不会保存你的REPL,而take可能仍然太多。

为了避免意外打印完整的序列号,可以将 *print-length* 设置为合理的数字:

(set! *print-length* 5)

因此,现在可以安全地打印整个斐波那契序列,该序列将仅打印前 5 个元素,后跟点:

user=> (set! *print-length* 5)
5
user=> fibs
(0 1 1 2 3 ...)

*print-length*适用于要在REPL中打印的所有序列,但对于非常大的标签序列特别有用。

从java存储创建浅序列和深序列

很多clojure函数采用序列(而不是本机java数据库)作为参数。java本机数据库可以使用浅序列包装,如下所示:

>>> (def pixels (into-array (range 10)))
#'user/pixels
>>> pixels
[Ljava.lang.Integer;@f30d8e
>>> (def seq-pix (seq pixels))
#'user/seq-pix
>>> seq-pix
(0 1 2 3 4 5 6 7 8 9)

现在,如果我们修改本机阵列,则读取时序列又反映该改变:

>>> (aset pixels 3 99)
99
>>> seq-pix
(0 1 2 99 4 5 6 7 8 9)

该数据库重复。创建的唯一新对象是浅序列:

>>> (class seq-pix)
clojure.lang.ArraySeq

要创建数据库的真正重要副本,可以执行以下操作:

>>> (def pixels2 (vec pixels))
#'user/pixels2
>>> (class pixels2)
clojure.lang.LazilyPersistentVector
>>> pixels2
[0 1 2 99 4 5 6 7 8 9]
>>> (def seq-pix2 (seq pixels2))
#'user/seq-pix2
>>> (class seq-pix2)
clojure.lang.APersistentVector$Seq

或者,简而言之:

(def seq-pix2 (seq (vec pixels)))
#'user/seq-pix2

因此,现在对原始pixels内存的任何更改都不会影响新序列:

>>> (aset pixels 3 101)
101
>>> seq-pix2
(0 1 2 99 4 5 6 7 8 9)
  • 感谢 irc.freenode.net 上的 Chouser 和 wwmorgan 提供的 #clojure 示例*

从clojure代码生成java类

使用带有 gen-class 的提前 (AOT) 编译,任何 clojure 代码都可以编译为 java 类。然后可以从 java 代码或任何脚本语言(如 jythonjrubyjavascriptany other)中使用。

一种方法是在命名空间块中放置gen-class声明。

请注意:命名以及空间必须匹配.clj文件所在的文件夹结构.clj文件的文件名。例如,要生成名为fj.tests.process.FloatProcessorPlus的类,您需要fj/tests/process/FloatProcessorPlus.clj下的clojure文件。

一般 clojure 代码编译为 .class文件,您需要:

1.运行clojure.lang.Repl的当前目录中的classes/文件夹。此文件夹将接收生成的.class文件。

  1. 将顶级文件夹(在示例中为“fj”文件夹)以及包含.clj文件本身的文件夹添加到类路径中。
  2. 将classes/文件夹添加到您的类路径中。
  3. 在 clojure.lang.Repl 中,使用 compile 函数。

例如:

$ mkdir classes
$ java -cp .:../../ij.jar:../../jars/clojure.jar:./classes/:./fj/tests/process/ clojure.lang.Repl
user=> (compile 'fj.tests.process.FloatProcessorPlus)
fj.tests.process.FloatProcessorPlus
user=>

以下 clojure 示例包含一个命名空间声明,其中包括一些导入以及 gen 类。在 gen-class 块中,我们定义了代码扩展哪个类(在本例中为 ij.process.FloatProcessor),以及要创建哪些方法(具有特定的参数签名和返回对象签名)。

稍后,编译器默认每个声明的方法分配一个 clojure 函数,使用蓝牙字符串加上方法名称来匹配函数。

例如,对于其他 fp- 和方法 fillValue,编译器将查找 clojure 函数fp-fillValue

最后,main 方法不是直接声明的,但如果名为存在 prefix + main 的函数(示例中为fp-main),则该方法也存在。我们可以使用 main 方法将新类作为应用程序运行。

Clojure示例代码:

; Albert Cardona 20081203
; Save this file as fj/tests/process/FloatProcessorPlus.clj

; and compile it from a Repl or clojure script like:
;
; (compile 'fj.tests.process.FloatProcessorPlus)
;
; Be sure to set the classpath to point to the folder containing the above file, for example:
; $ cd fiji/plugins/
; $ mkdir -p tests/fj/tests/process
; $ cd tests/fj/tests/process/
; $ vim FloatProcessorPlus.clj
; ...
; $ cd ../../../
; $ mkdir classes
; $ java -cp ../../ij.jar:../../jars/clojure.jar:./classes/:.:./fj/tests/process/ clojure.lang.Repl
; user=> (compile 'fj.tests.process.FloatProcessorPlus)
; fj.tests.process.FloatProcessorPlus
; user=>
;
; The compilation will place the proper `.class` files under the proper directory
; structure in the ./classes/ folder.
;
; Then run like any other java class with a static public void main method:
; $ java -cp .:../../ij.jar:../../jars/clojure.jar:./classes fj.tests.process.FloatProcessorPlus
;

(ns fj.tests.process.FloatProcessorPlus
   (:import (ij ImagePlus)
            (ij.process FloatProcessor)
            (java.util Random))
   (:gen-class
    ; Could also use :implements
    :extends ij.process.FloatProcessor
    ; Specify methods to expose as public,
    ; with specific parameter types and return type:
    :methods [[fillMin [] void]
              [fillMax [] void]
              [fillValue [float] void]
              [randomize [] void]]
    ; Define a function prefix for the exposed methods: for example,
    ; the fillMin public method is implemented by function fp-fillMin.
    :prefix "fp-"))

(defn fp-fillValue [this value]
  "Set each pixel in the image to the given value."
  (.setPixels this
              (into-array Float/TYPE (replicate
                            (* (.getWidth this) (.getHeight this))
                            (float value)))))

(defn fp-fillMin [this]
  (.fillValue this Float/MIN_VALUE))

(defn fp-fillMax [this]
  (.fillValue this Float/MAX_VALUE))

; Declaring a function to be used as a java method, within a closure:
(let [generator (Random. (System/currentTimeMillis))]
  (defn fp-randomize [this]
    (.setPixels this (into-array Float/TYPE
                            (map
                              (fn [x] (.nextFloat generator))
                              (range (* (.getWidth this) (.getHeight this))))))))


; This function is seen as the static public void main function of a java class:
; (add a parameter, like [args], if you would like to access the command-line args)
(defn fp-main []
  "Test the generated class"
  (let [imp (ImagePlus. "Test" (fj.tests.process.FloatProcessorPlus. 100 100))
        ip (.getProcessor imp)] ; Testing access on "ImageProcessor" type
    (.show imp)
    ; Test some methods of our extended FloatProcessor class:
    (.randomize ip)
    (.findMinAndMax ip)
    (.updateAndDraw imp)))

引用、并发、事务和同步

Clojure 支持线程并发,需要显着式锁定。与 java 相比,这是一个巨大的代码进步:锁,尤其是多个锁,很难正确使用,也很难正确调试(但请参阅debugging multithreaded java programs)。

沟通的构建块是references,它们是使用ref函数创建的,并使用commutealter函数(以及others)在交易块(由dosync定义)内进行修改。

要写入引用的值,请调用 deref 或仅调用 @

; Create a new reference named 'id' initializated to value zero:
(def id (ref 0))
-> 0

; Read out the value of the reference:
@id
-> 0

; Increase the id by one, using built-in function "inc":
(dosync (alter id inc))
-> 1

; Set the value to 20 (ignoring the current value, given in cv:
(dosync (alter id (fn [cv] 20)))
-> 20

引用不是特定于相同类型的:任何对象都可以分配给定的引用。这是否有意义取决于您。

commutealter函数将引用的值替换为作为参数给出函数的返回值。作为commutealter的参数给出的函数依次给出引用的值(即取消引用的引用)和任何其他进一步的参数。commutealter之间的区别在于,commute在事务完成后返回取消引用的引用,该引用可能已经与事务中设置为引用的值不同(因为并发);而alter返回交易完成时其所拥有的值(即函数返回的值,与设置为引用的值相同)。

在下面的示例中,唯一的 id 连续递增 1,并且所有 id 都无序地收集到一个保护中。下一个可用的 id 和所有访问的 id 的保护都在存储引用中。

请记住,分配给名为“ls”的引用的 ids 保护始终是不可变的:我们分配给下面的引用“ls”还是一个新的保护,是通过将新的 id 添加到旧的 ids 保护而产生的。这种不变性使其他线程能够在没有锁的情况下读取保护。为了提高性能,请记住,保护与其他 clojure 数据结构相同,具有结构共享,因此新的保护不是重复项,即使是类似的。

分配是在事务中完成的,因此无论有多少线程尝试,生成的服务都将具有所有 id。

; Albert Cardona 2008-12-18
; Example clojure program using references and concurrent threads
; that alter the value of the references.
;
; 10 threads running concurrently
; each thread runs 100000 iterations
; in each iteration the thread increments a counter 'id'
;   and adds it to a list 'ls' of ids.
; At the end, we print the current value of 'id'
; and the length of the list 'ls' of ids.
;
; No locks!

(ns fj.test.concurrent
  (:import (java.util.concurrent Executors TimeUnit)))


(let [ls (ref []) ; A reference to a vector storing a list if ids.
      id (ref 0)  ; The next unique id available.
      n_threads 10
      n_iterations 100000
      exec (Executors/newFixedThreadPool n_threads)]
  (println "Running" n_threads "threads x" n_iterations "iterations/thread...")
  (dotimes [i n_threads]
    (.submit exec (fn []
                    (dotimes [t n_iterations]
                      ; Obtain the next unique id:
                      ; (Note we use "alter" and not "commute", because alter
                      ; returns the result of the applied function, whereas
                      ; commute would return the dereferenced ref, which could
                      ; have already changed.  Thanks to AWizzards for spotting
                      ; this.)
                      (let [next-id (dosync (alter id inc))]
                        ; Create a new vector made of
                        ;  all previous ids and next-id,
                        ;  and set it as the current list of ids:
                        (dosync (commute ls conj next-id)))))
                   nil))
  (.shutdown exec)
  (.awaitTermination exec 10 TimeUnit/MINUTES)
  (println "... done!")
  ; If there was any clash in setting the reference to the list of ids,
  ; the count would be less than 1000000:
  (println "Number of listed ids:" (count @ls))
  ; If any id was used twice, the next available id would be less than 1000000:
  (println "Next available id:" @id)
  ; Check that there aren't any repeated ids:
  (println "Number of repeated ids:"
           (- (count @ls)
              (count (set @ls))))) ; Make a hash set (with unique entries) from the list of ids

使用 try/catch/finally 并发送异常

(try
  (println "Going to throw ...")
  (throw (Exception. "Testing error catching"))
  (println "Should not print, an Exception is thrown before")
  (catch Exception e
    (println "Oops ... an error ocurred.")
    (.printStackTrace e))
  (finally
    (println "Cleaning up!")))

当然,您可以抛出任何您想要的异常。例如,在检查函数参数时:

(import '(java.awt Rectangle))

(defn area [#^Rectangle r]
  (if (not (instance? Rectangle r))
    (throw (IllegalArgumentException. "Can only compute the area of a Rectangle.")))
  (* (.width r) (.height r)))

上面,虽然有类型声明,但我们可以将任何值传递给 area 函数,它仍然可以工作,但是我们的类检查当然会减少执行:

user=> (area 10)
java.lang.IllegalArgumentException: Can only compute the area of a Rectangle. (NO_SOURCE_FILE:0)
user=> (area (Rectangle. 0 0 10 10))
100

在 shell 中执行命令并捕获其输出

首先我们定义宏exec

(import '(java.io BufferedReader InputStreamReader))

(defmacro exec
  "Execute a command on the shell, passing to the given function
   the lazy sequence of lines read as output, and the rest of arguments."
  [cmd pred & args]
  `(with-open [br# (BufferedReader. (InputStreamReader. (.getInputStream (.exec (Runtime/getRuntime) ~cmd))))]
    (~pred (line-seq br#) ~@args)))

对上述宏语法的一些解释(另见clojure’s macro syntax page):

  • 反引号 ` quotes the next expression, as defined by: `( )。这意味着代码块*不*被评估。但是,与简单的引号 ' 不同,当用 \~(波形符)标记时,反引号可以对内块的表达式进行求值。
  • ~(波形符)计算立即表达式。只能在反引用代码块的上下文中使用。
  • ~@ 表示评估和扩展,效果是列表的元素放置在代码中,就像它们在代码中声明的那样,而占用列表封装。因此: `(~@(str “this” “that”)) results in: “thisthat”. In the example above, we expand the & args, which is a list containing all arguments given to the exec macro beyond the first and second (which are bound to cmd and pred, respectively). In this way, we lay down the proper function call of the pred, which is expected to be a function name (a predicate); the reason we use ~ on it is to evaluate pred so that it renders the pointer to the function itself. That pred函数,根据设计,必须接受文本行的序列以及之后的任何数量的参数。
  • 名称标记的#扩展为(gensym name),这会创建一个唯一命名的符号,以色列名称冲突。
  • 反引号之外的任何代码(在上面的情况下没有)都将在宏读取时执行,而不是在代码执行时(也称为运行时)执行!因此,在编写将在运行时执行的代码之前,可以进行任何预计算。

然后我们给宏一个要执行的命令和一个处理其stdout输出的函数。

; List all files in the home directory:
(exec "ls /home/albert/"
      #(doseq [line %1] (println line)))

第二个示例,打印主目录中每个上市的文件的文件大小:

; Print the size of each file in the home directory:
(import '(java.io File))

(let [dir "/home/albert/"]
  (exec (str "ls " dir)
        #(doseq [line %1]
          (println (.length (File. (str dir line)))))))

第三个示例,告诉音乐播放器 XMMS2 跳转到其播放列表中的特定曲目:

(let [track-number 125]
  (exec (str "xmms2 jump " track-number)
        (fn [lines] lines)))

以上是 XMMS2 的 clojure GUI 的摘录,可以在 github xmms2-gui 上获取。

创建函数的导数

函数的导数:

\[D f(x) = f'(x) = \lim_{dx\rightarrow 0}\frac{f(x + dx) - f(x)}{dx}\]

我们可以通过选择任意精确的增量dx值来近似导数。

因此,首先我们定义一个函数,它接受任何函数作为参数,并返回一个实现其导数的新函数。为了方便起见,我们在一个闭包中定义它,指定任意精确的增量dx,但我们可以将它作为参数传递):

(let [dx (double 0.0001)]
  (defn derivative [f]
     "Return a function that is the derivative of the given function f, using dx increments."
     (fn [x]
        (/ (- (f (+ (double x) dx))
              (f x))
            dx))))

然后,对于任何示例函数,例如 x 的立方:

(defn cubic [x]
  (let [a (double x)]
    (* a a a)))

…我们创建它的导函数,将其输入变量放入其中(注意我们使用def而不是defn):

(def cubic-prime (derivative cubic))

现在我们可以像调用任何其他函数一样调用三次素数函数:

(cubic-prime 2)
-> 12.000600010022566
(cubic-prime 3)
-> 27.00090001006572

(cubic-prime 4)
-> 48.00120000993502

x^3 的导数是 3 * x^2,对于 4 的 x 等于 48。我们的导数与增量 dx 的值一样精确。

上面的代码翻译自 funcall blog 的 lisp 代码。感谢Joe Marshall分享这个perl。

漂亮地打印一个原始堆栈

假设我们创建一个长度为 10 的原始磁盘:

user=> (def pa (make-array Integer/TYPE 10))

如果我们打印它,我们会得到:

user=> (println pa)
#<int[] [I@169bc15>

…这不太有用。正好,让我们漂亮地打印它。

首先,从 clojure-contrib pprint 调用空间导入函数 pprint(以及许多其他函数):

user=> (use 'clojure.contrib.pprint)

然后,使用它:

user=> (pprint pa)
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

通过使用 seq 打包原始磁盘获取类似的结果,这会在原始磁盘上生成一个集合 视图:

user=> (println (seq pa))
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

seq仅创建一个视图(而不是副本),您可以说服自己:更改阵列同时更改视图:

user=> (def sa (seq pa))
user=> sa
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
user=> (aset pa 3 7)
user=> (pprint pa)
[0, 0, 0, 7, 0, 0, 0, 0, 0, 0]
user=> sa
[0, 0, 0, 7, 0, 0, 0, 0, 0, 0]

将图像文件加载到字节存储中

(import [java.io File FileInputStream]
(defn ^bytes load-file
  "Load a file into a byte array."
  [filepath]
  (let [^File f (File. filepath)
        len (int (.length f))
        ^bytes b (byte-array len)]
    (with-open [^FileInputStream fis (FileInputStream. f)]
      (loop [offset (int 0)]
        (if (< offset len)
          (recur (unchecked-add offset (.read fis b offset (unchecked-subtract len offset)))))))
     b))

…然后可以将其解析为java.awt.Image

(def img (javax.imageioImageIO/read
               (java.io.ByteArrayInputStream.
                 (load-file "/home/acardona/Desktop/t2/NileBend.jpg"))))

…然后可以显示为 ImagePlus

(.show (ij.ImagePlus. "nile bend" img))