Search code examples
jythonimagejjava

Importing external libraries/Packages from an ImageJ/Fiji script


I am writing an imageJ/Fiji plugin in Jython using the Fiji script editor. I need to import external numerical libraries such as ParallelColt to help me handle multidimensional matrices.

I began with ParallelColt by placing its jar file in the java folder inside Fiji: "Fiji.app/java/macosx-java3d/Home/lib/ext” Then I tried importing it by writing: "import ParallelColt” or more specifically "from ParallelColt import jplasma" And I get an error of module not found.

I tried placing the jar inside the Fiji plugins folder instead but with still no success. I also tried by using the folder with all the java classes of ParallelColt instead of the jar file and I still was not able to import the classes from my script.

The question perhaps that I am simply asking is how to import java libraries from a Jython script. Any help will be greatly appreciated.


Solution

  • There are two issues here.

    1. Dependencies

    In order to depend on a complex third party software project with multiple JAR files, you need to make sure you have placed all the dependencies of the project (i.e., all the JAR files it needs) into ImageJ.app/jars.

    One way to do it is to find the dependencies on Maven Central and then use a build tool such as Maven or Gradle to download them.

    In your case, you can do this with Maven by crafting the following pom.xml file:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>dummy</groupId>
      <artifactId>dummy</artifactId>
      <version>0.0.0-SNAPSHOT</version>
    
      <dependencies>
        <dependency>
          <groupId>net.sourceforge.parallelcolt</groupId>
          <artifactId>parallelcolt</artifactId>
          <version>0.10.1</version>
        </dependency>
        <dependency>
          <groupId>net.mikera</groupId>
          <artifactId>vectorz</artifactId>
          <version>0.34.0</version>
        </dependency>
      </dependencies>
    
    </project>
    

    Then executing:

    mvn dependency:copy-dependencies
    

    This will result in the following files in the target/dependencies folder:

      29087 AMDJ-1.0.1.jar
       8687 BTFJ-1.0.1.jar
      19900 COLAMDJ-1.0.1.jar
      55638 JKLU-1.0.0.jar
    1194003 arpack_combined_all-0.1.jar
      31162 core-lapack-0.1.jar
     212836 csparsej-1.1.1.jar
      88639 edn-java-0.4.4.jar
      91793 jplasma-1.2.0.jar
     762778 jtransforms-2.4.0.jar
     237344 junit-4.8.2.jar
       8693 mathz-0.3.0.jar
     131210 netlib-java-0.9.3.jar
      66134 optimization-1.0.jar
    4111744 parallelcolt-0.10.1.jar
      14028 randomz-0.3.0.jar
     593015 vectorz-0.34.0.jar
    

    Move these into your ImageJ.app/jars folder, and you should be good to go—though beware of multiple versions of the same library, since ImageJ cannot handle that right now.

    2. Class references

    The line from ParallelColt import jplasma is not valid. That is, there is no ParallelColt.jplasma Java class or package. You can verify this yourself from the command line:

    $ find-class() { for f in *.jar; do result=$(jar tf "$f" | grep -l "$@"); if [ "$result" != "" ]; then echo $f; fi; done; }
    $ cd ImageJ.app/jars
    $ find-class jplasma
    

    Two JAR files have classes containing that string:

    core-lapack-0.1.jar
    jplasma-1.2.0.jar
    

    And taking a look inside of them quickly reveals that the package names do not start with ParallelColt:

    $ jar tf jplasma-1.2.0.jar | grep jplasma
    edu/emory/mathcs/jplasma/
    edu/emory/mathcs/jplasma/benchmark/
    edu/emory/mathcs/jplasma/test/
    edu/emory/mathcs/jplasma/tdouble/
    edu/emory/mathcs/jplasma/example/
    edu/emory/mathcs/jplasma/benchmark/DgelsBenchmark.class
    edu/emory/mathcs/jplasma/benchmark/DposvBenchmark.class
    edu/emory/mathcs/jplasma/benchmark/DgesvBenchmark.class
    edu/emory/mathcs/jplasma/Barrier.class
    edu/emory/mathcs/jplasma/test/DposvTest.class
    edu/emory/mathcs/jplasma/test/DgelsTest.class
    edu/emory/mathcs/jplasma/test/DgesvTest.class
    edu/emory/mathcs/jplasma/tdouble/Dgels.class
    edu/emory/mathcs/jplasma/tdouble/Dgeqrf.class
    edu/emory/mathcs/jplasma/tdouble/Dglobal$Dplasma_cntrl.class
    edu/emory/mathcs/jplasma/tdouble/Dplasma.class
    ...
    edu/emory/mathcs/jplasma/tdouble/Dglobal$Dplasma_aux.class
    edu/emory/mathcs/jplasma/tdouble/Pdormqr.class
    edu/emory/mathcs/jplasma/example/DposvExample.class
    edu/emory/mathcs/jplasma/example/DgesvExample.class
    edu/emory/mathcs/jplasma/example/DgelsExample.class
    

    Rather, if you would like to use, e.g., edu.emory.mathcs.jplasma.tdouble.Dplasma, you need to import it as:

    from edu.emory.mathcs.jplasma.tdouble import Dplasma
    

    Here is a port of DgelsExample.java from the JPlasma distribution which works in the ImageJ Script Editor:

    from edu.emory.mathcs.jplasma.tdouble import Dplasma
    from java.lang import Math
    import jarray
    
    M = 15;
    N = 10;
    NRHS = 5;
    
    A = jarray.zeros(M * N, 'd')
    B = jarray.zeros(M * NRHS, 'd')
    
    # Initialize A
    for i in range(0, M):
        for j in range(0, N):
            A[M * j + i] = 0.5 - Math.random()
    
    # Initialize B
    for i in range(0, M):
        for j in range(0, NRHS):
            B[M * j + i] = Math.random()
    
    # Plasma Initialize
    Dplasma.plasma_Init(M, N, NRHS)
    
    # Allocate T
    T = Dplasma.plasma_Allocate_T(M, N)
    
    # Solve the problem
    INFO = Dplasma.plasma_DGELS(Dplasma.PlasmaNoTrans, M, N, NRHS, A, 0, M, T, 0, B, 0, M)
    
    # Plasma Finalize
    Dplasma.plasma_Finalize()
    
    if INFO < 0:
        print("-- Error in DgelsExample example !")
    else:
        print("-- Run successfull !")
    

    See the Jython Scripting page of the ImageJ Wiki for further information on writing Jython scripts.

    One final parting comment: the ImageJ Script Editor was really designed as a tool to develop single-class scripts and plugins that rely on components already present in the installation. My suggestion for a project like this with many dependencies would be to instead code in Java using an IDE such as Eclipse, because such IDEs offer many more productivity enhancing features. You can use AST-based auto-complete to explore the API of the libraries you're using, and browse javadocs and sources easily without doing web searches.