We're Hiring!

problem with new FormatReader that calls native/JNI dll

General and open developer discussion about using OMERO APIs from C++, Java, Python, Matlab and more! Please new questions at https://forum.image.sc/tags/omero
Please note:
Historical discussions about OMERO. Please look for and ask new questions at https://forum.image.sc/tags/omero

If you are having trouble with custom code, please provide a link to a public repository, ideally GitHub.

problem with new FormatReader that calls native/JNI dll

Postby richard3i » Tue Oct 07, 2014 9:13 pm

I am trying to implement a new SlideBook reader which uses a JNI wrapper DLL to access a .SLD reader DLL called SBReadFile.dll . I have modeled my BioFormats reader class after LegacyND2Reader .

I've put my native .dlls in the lib\ folder along with LegacyND2Reader.dll, is there anything else I need to do so that they will get packaged and used properly?

My reader seems to successfully find/load the native JNI wrapper DLL (SBReadFileJNI.dll) using the static method:

Code: Select all
  static {
    try {
      System.loadLibrary("SBReadFileJNI");
    }
    catch (UnsatisfiedLinkError e) {
      LOGGER.trace(NO_3I_MSG, e);
      libraryFound = false;
    }
    catch (SecurityException e) {
      LOGGER.warn("Insufficient permission to load native library", e);
    }
  }


But when the native closeFile method is called an UnsatisfiedLinkError exception is thrown.

Code: Select all
Exception in thread "main" java.lang.UnsatisfiedLinkError: loci.formats.in.Slide
Book6Reader.closeFile()V
        at loci.formats.in.SlideBook6Reader.closeFile(Native Method)
        at loci.formats.in.SlideBook6Reader.close(SlideBook6Reader.java:232)
        at loci.formats.ImageReader.close(ImageReader.java:536)
        at loci.formats.ReaderWrapper.close(ReaderWrapper.java:339)
        at loci.formats.ReaderWrapper.close(ReaderWrapper.java:339)
        at loci.formats.FileStitcher.close(FileStitcher.java:519)
        at loci.formats.in.FilePatternReader.close(FilePatternReader.java:241)
        at loci.formats.ImageReader.close(ImageReader.java:536)
        at loci.formats.ImageReader.close(ImageReader.java:757)
        at loci.formats.ReaderWrapper.close(ReaderWrapper.java:573)
        at loci.formats.tools.ImageInfo.configureReaderPreInit(ImageInfo.java:43
6)
        at loci.formats.tools.ImageInfo.testRead(ImageInfo.java:988)
        at loci.formats.tools.ImageInfo.main(ImageInfo.java:1074)


I am stuck and could use some advice on how to debug what is wrong.

I am testing using the command: showinfo slide1.sld and the log output looks like:

Code: Select all
C:\Users\Richard\Documents\3i\git\tools>showinf ..\..\slide1.sld
Checking file format [3i SLD (native)]
Initializing reader


Perhaps someone can spot a problem in the reader file (attached).

Thanks!

-- Richard

Code: Select all
package loci.formats.in;

import java.io.EOFException;
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Vector;

import loci.common.Constants;
import loci.common.DataTools;
import loci.common.RandomAccessInputStream;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatReader;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataStore;

import ome.xml.model.primitives.PositiveFloat;

import loci.formats.MissingLibraryException;

/**
* SlideBook6Reader is a file format reader for 3i SlideBook SLD files that uses
* the SlideBook SBReadFile SDK - it is only usable on Windows machines.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/in/LegacyND2Reader.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/LegacyND2Reader.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public class SlideBook6Reader  extends FormatReader {

  // -- Constants --

  /** Modality types. */
  private static final int WIDE_FIELD = 0;
  private static final int BRIGHT_FIELD = 1;
  private static final int LASER_SCAN_CONFOCAL = 2;
  private static final int SPIN_DISK_CONFOCAL = 3;
  private static final int SWEPT_FIELD_CONFOCAL = 4;
  private static final int MULTI_PHOTON = 5;

  private static final String URL_3I_SLD =
    "http://www.openmicroscopy.org/site/support/bio-formats/formats/3i-slidebook6-sld.html";
  private static final String NO_3I_MSG = "3i SlideBook SBReadFile library not found. " +
    "Please see " + URL_3I_SLD + " for details.";
    private static final String GENERAL_3I_MSG = "3i SlideBook SBReadFile library problem. " +
            "Please see " + URL_3I_SLD + " for details.";

  // -- Static initializers --

  private static boolean libraryFound = true;

  static {
    try {
      System.loadLibrary("SBReadFileJNI");
    }
    catch (UnsatisfiedLinkError e) {
      LOGGER.trace(NO_3I_MSG, e);
      libraryFound = false;
    }
    catch (SecurityException e) {
      LOGGER.warn("Insufficient permission to load native library", e);
    }
  }

  // -- Constructor --

  public SlideBook6Reader() {
    super("3i SLD (native)", new String[] {"sld"});
    domains = new String[] {FormatTools.LM_DOMAIN};
  }

  // -- IFormatReader API methods --

  /* @see IFormatReader#isThisType(String, boolean) */
  public boolean isThisType(String file, boolean open) {
    // Check the first few bytes of a file to determine if the file can be read by this reader.
    // You can assume that index 0 in the stream corresponds to the index 0 in the file.
    // Return true if the file can be read; false if not (or if there is no way of checking).
    return libraryFound && super.isThisType(file, open);
  }

  /**
   * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
   */
  public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
    throws FormatException, IOException
  {
    // Returns a byte array containing the pixel data for a subimage specified image from the given file.
    // The dimensions of the subimage (upper left X coordinate, upper left Y coordinate, width, and height) are
    // specified in the final four int parameters. This should throw a FormatException if the image number is
    // invalid (less than 0 or >= the number of images). The ordering of the array returned by openBytes should
    // correspond to the values returned by isLittleEndian() and isInterleaved(). Also, the length of the byte array
    // should be [image width * image height * bytes per pixel]. Extra bytes will generally be truncated. It is
    // recommended that the first line of this method be:
    //    FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h) - this ensures that all of the parameters
    // are valid.

    FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);

    int[] zct = FormatTools.getZCTCoords(this, no);
    int bpc = FormatTools.getBytesPerPixel(getPixelType());
    byte[] b = new byte[FormatTools.getPlaneSize(this)];

    readImagePlaneBuf(b, getSeries(), 0, zct[2], zct[0], zct[1]);

    int pixel = bpc * getRGBChannelCount();
    int rowLen = w * pixel;
    for (int row=0; row<h; row++) {
      System.arraycopy(b, pixel * ((row + y) * getSizeX() + x), buf,
        row * rowLen, rowLen);
    }

    if (isRGB()) {
      int bpp = getSizeC() * bpc;
      int line = w * bpp;
      for (int row=0; row<h; row++) {
        for (int col=0; col<w; col++) {
          int base = row * line + col * bpp;
          for (int bb=0; bb<bpc; bb++) {
            byte blue = buf[base + bpc*(getSizeC() - 1) + bb];
            buf[base + bpc*(getSizeC() - 1) + bb] = buf[base + bb];
            buf[base + bb] = blue;
          }
        }
      }
    }
    return buf;
  }

  // -- Internal FormatReader API methods --

  /* @see loci.formats.FormatReader#initFile(String) */
  protected void initFile(String id) throws FormatException, IOException {
    super.initFile(id);

    // The majority of the file parsing logic should be placed in this method. The idea is to call this method
    // once (and only once!) when the file is first opened. Generally, you will want to start by calling
    // super.initFile(String). You will also need to set up the stream for reading the file, as well as initializing
    // any dimension information and metadata. Most of this logic is up to you; however, you should populate the ‘core’
    // variable (see loci.formats.CoreMetadata).

    // Note that each variable is initialized to 0 or null when super.initFile(String) is called. Also,
    // super.initFile(String) constructs a Hashtable called "metadata" where you should store any relevant metadata.

    try {
      openFile(id);
      int numSeries = getNumCaptures();

      core.clear();
      for (int i=0; i<numSeries; i++) {
        CoreMetadata ms = new CoreMetadata();
        core.add(ms);
        ms.sizeX = getNumXColumns(i);
        if (ms.sizeX % 2 != 0) ms.sizeX++;
        ms.sizeY = getNumYRows(i);
        ms.sizeZ = getNumZPlanes(i);
        ms.sizeT = getNumTimepoints(i);
        ms.sizeC = getNumChannels(i);
        int bytes = getBytesPerPixel(i);
        if (bytes % 3 == 0) {
          ms.sizeC *= 3;
          bytes /= 3;
          ms.rgb = true;
        }
        else ms.rgb = false;

        ms.pixelType = FormatTools.pixelTypeFromBytes(bytes, false, true);
        ms.imageCount = ms.sizeZ * ms.sizeT;
        if (!ms.rgb) ms.imageCount *= ms.sizeC;
        ms.interleaved = true;
        ms.littleEndian = true;
        ms.dimensionOrder = "XYCZT";
        ms.indexed = false;
        ms.falseColor = false;
      }
    }
    catch (UnsatisfiedLinkError e) {
      throw new MissingLibraryException(GENERAL_3I_MSG, e);
    }
    catch (Exception e) {
      throw new MissingLibraryException(GENERAL_3I_MSG, e);
    }

    MetadataStore store = makeFilterMetadata();
    MetadataTools.populatePixels(store, this);
    for (int i=0; i<getNumCaptures(); i++) {
      store.setImageName("Image " + (i + 1), i);
    }
  }

  public void close(boolean fileOnly) {
    // Cleans up any resources used by the reader. Global variables should be reset to their initial state, and any
    // open files or delegate readers should be closed.
      closeFile();
  }

  // -- Native methods --
   public native boolean openFile(String path);
   public native void closeFile();
   public native int getNumCaptures();   
   public native int getNumPositions(int inCapture);
   public native int getNumTimepoints(int inCapture);
   public native int getNumChannels(int inCapture);
   public native int getNumXColumns(int inCapture);
   public native int getNumYRows(int inCapture);
   public native int getNumZPlanes(int inCapture);
   public native int getElapsedTime(int inCapture, int inTimepoint);

   public native int getExposureTime(int inCapture, int inChannel);
   public native float getVoxelSize(int inCapture);

   public native double getXPosition(int inCapture, int inPosition);
   public native double getYPosition(int inCapture, int inPosition);
   public native double getZPosition(int inCapture, int inPosition, int inZPlane);
   
   public native int getMontageRow(int inCapture, int inPosition);
   public native int getMontageColumn(int inCapture, int inPosition);

   public native String getChannelName(int inCapture, int inChannel);
   public native String getLensName(int inCapture);
   public native double getMagnification(int inCapture);
   public native String getImageName(int inCapture);
   public native String getImageComments(int inCapture);

    public native int getBytesPerPixel(int inCapture);

   public native boolean readImagePlaneBuf( byte outPlaneBuffer[],
                     int inCapture,
                     int inPosition,
                     int inTimepoint,
                     int inZ,
                     int inChannel );
}

richard3i
 
Posts: 5
Joined: Tue Sep 30, 2014 10:11 am

Re: problem with new FormatReader that calls native/JNI dll

Postby mlinkert » Wed Oct 08, 2014 9:43 pm

Hi Richard,

The UnsatisfiedLinkError in this case almost always means that the function name in the .dll does not match what JNI is expecting.

I was able to reproduce the problem using your reader and current versions of SBReadFile.dll and SBReadFileJNI.dll. SBReadFileJNI.dll contains functions named e.g. _Java_SBReadFileJNI_closeFile - which means that the closeFile method is expected to be defined as a native method in the SBReadFileJNI class. You will need to regenerate SBReadFileJNI.dll from SlideBook6Reader.java so that the function names contain the correct Java class name; otherwise, there is no way of working out which Java native method maps to which .dll function.

You can see an example of what the names should look like in loci_formats_in_LegacyND2Reader.h and loci_formats_in_LegacyND2Reader.cpp. 'javah SlideBook6Reader.java' should create the correct header file, and then the implementation just needs to be updated to match.

If that doesn't solve the problem, please let us know.

-Melissa
User avatar
mlinkert
Team Member
 
Posts: 353
Joined: Fri May 29, 2009 2:12 pm
Location: Southwest Wisconsin

Re: problem with new FormatReader that calls native/JNI dll

Postby richard3i » Tue Oct 28, 2014 7:14 pm

Hi Melissa,

Thanks for the advice, you were right, I was using the wrong signature in my .h file for the SBReadFileJNI.dlls. I had to change it from
Code: Select all
Java_SBReadFileJNI_closeFile( ...etc

to
Code: Select all
Java_loci_formats_in_SlideBook6Reader_closeFile( ...etc


I will also rename SBReadFileJNI.dll to SlideBook6Reader.dll to make it clearer that this .dll is a specific interface file for the reader.

When I put the new .dlls (SBReadFile.Dll, SlideBook6Reader.dll and zlibwapi.dll) and newly generated loci-tools.jar in the same directory as showinf, then run showinf on a new slide - it works and opens my .sld file using the new dlls.

But when I put the new bioformats_package-5.1.0-SNAPSHOT.jar in the plugin directory of imageJ it gives me the error:

loci.formats.UnknownFormatException: Unknown file format: C:\Users\Richard\Documents\3i\test-filters.sld

For some reason it runs the old SlideBookReader plugin and ignores the new SlideBook6Reader plugin; if from the configuration I tell it to ignore the original SlideBookReader then it doesn't even try the new one, despite it showing up in the list.

Any advice on how to move from getting it working with showinf to getting it working as an ImageJ plugin?

Also, when I look in the jar I don't see the dlls anyway, is there a special way to package those into the jar. I also have both x32 and x64 dlls and I don't see how I can specify which to load.

Thanks! I'm very happy to have it working with showinf so from here it's hopefully not too hard.

PS> I can send you a zip of the relevant files if you have time to try it out.
richard3i
 
Posts: 5
Joined: Tue Sep 30, 2014 10:11 am

Re: problem with new FormatReader that calls native/JNI dll

Postby sbesson » Mon Nov 03, 2014 10:28 am

Hi Richard,

yes at this point looking at the code is certainly necessary. You can upload the zip file containing your code to http://qa.openmicroscopy.org.uk/qa/upload/.

Best,
Sebastien
User avatar
sbesson
Team Member
 
Posts: 421
Joined: Tue Feb 28, 2012 7:20 pm

Re: problem with new FormatReader that calls native/JNI dll

Postby jmoore » Thu Nov 13, 2014 8:49 am

Hi Richard,

what might be happening is that the readers.txt file in the bioformats_package.jar is overriding your addition to that file:

Code: Select all
loci.formats.in.SlideBook6Reader      # sld
loci.formats.in.SlidebookReader       # sld


You might try modifying the file in the jar, or using `showinf -format Slidebook6Reader ...`

Cheers,
~Josh.
User avatar
jmoore
Site Admin
 
Posts: 1591
Joined: Fri May 22, 2009 1:29 pm
Location: Germany

Re: problem with new FormatReader that calls native/JNI dll

Postby richard3i » Mon Nov 24, 2014 7:36 pm

Hi Josh,

Thanks for the suggestion. It works fine with showinf, but does not work with the ImageJ plugin. The readers.txt file looks correct.

-- Richard
richard3i
 
Posts: 5
Joined: Tue Sep 30, 2014 10:11 am

Re: problem with new FormatReader that calls native/JNI dll

Postby jmoore » Tue Nov 25, 2014 9:32 am

Hi Richard,

I might be wrong, but my assumption would be that you're dealing with more than one readers.txt in which case it comes down to classpath resolution.

~Josh.
User avatar
jmoore
Site Admin
 
Posts: 1591
Joined: Fri May 22, 2009 1:29 pm
Location: Germany

Re: problem with new FormatReader that calls native/JNI dll

Postby mlinkert » Tue Nov 25, 2014 11:53 pm

Hi Richard,

ImageJ most likely cannot find the .dll files - you will need to make sure that the java.library.path environment variable is correctly set when ImageJ starts.

To avoid this sort of problem, we use the native-lib-loader library to load .dll/.so/.dylib files from a .jar - this means that the file can just be placed in the ImageJ plugins folder without having to set any environment variables. An example of how to package up the native libraries:

https://github.com/openmicroscopy/biofo ... /turbojpeg

and how to use the library once it has been packaged into a .jar:

https://github.com/openmicroscopy/biofo ... .java#L109

If that doesn't solve the problem, please let us know.

Regards,
-Melissa
User avatar
mlinkert
Team Member
 
Posts: 353
Joined: Fri May 29, 2009 2:12 pm
Location: Southwest Wisconsin

Re: problem with new FormatReader that calls native/JNI dll

Postby richard3i » Fri Dec 12, 2014 4:40 pm

Thanks for the tips Melissa. It turns out there were a few small code problems in my class, but ultimately I got it to work. I haven't tried the native lib loader approach yet, but that's the next step.

We're also doing more testing on different data. If anyone would like to try it out, just let me know and I can give you the files.

Once it's tested I guess the next step would be to do a pull request via git right? or is there some other things we should implement or test first?

All the best,

-- Richard
richard3i
 
Posts: 5
Joined: Tue Sep 30, 2014 10:11 am

Re: problem with new FormatReader that calls native/JNI dll

Postby hflynn » Mon Dec 15, 2014 11:50 am

Hi Richard,

Test data is always appreciated, you can upload it on our QA site, or if the files are too big, Melissa can give you ftp server instructions by PM.

You should find all the info you need for submitting a pull request on our contributing documentation and the further links provided there.

If you need any further help, please just ask,

Helen
hflynn
 
Posts: 97
Joined: Tue Sep 25, 2012 1:59 pm
Location: Dundee


Return to Developer Discussion

Who is online

Users browsing this forum: No registered users and 1 guest