Java tutorial

Getting started with Java

This tutorial was contributed by Merijn Van Erp of UMC Radboud in Nijmegen. Merijn is a scientific programmer is in the group of Jeroen van der Laak (of Camelyon fame) and has been using PMA.start for a variety of image analysis applications.

You need the sourcecode of the HistoJ plugin to work through this tutorial.

 

In order to use the PMA.start local server within a Java plugin for Fiji, have a look at the HistoJLite standard plugin. It contains all the necessary information needed to contact the PMA.start server, to get the information on the image needed without having to open the full image, and to fetch a (part of) the image at the proper scale.

The first thing to remember is to create a client. This is done via a standard port:

 

new PmaCoreClient("http://localhost:54001/");

With this client, it becomes possible to fetch the information on the image without loading the image itself. This is especially important when using the very large whole slide images. To fetch the image information, one first needs a file name; however, it is important to note that for the PMA client, the file name needs to include the full station name as given by a FileSystemView and use ‘/’ as file separator independent if the actual file system.

The easiest solution for this is to use the HistoJFileSystemView and the accompanying HistoFileChooser. These will give you the appropriate file name except that you still may need to replace the file separator. The drawback is that using your own default directory is tricky with these classes. It is possible to use another file chooser (the standard JFileChooser for example), but be aware that you need to get the correct station assignment:

 

this.pathoruid = file.getPath().replace("\\", "/");  // Replace for Windows system example
int i = pathoruid.indexOf("/");

String stationName =FileSystemView.getFileSystemView().getSystemDisplayName(new
File(this.pathoruid.substring(0, i) + File.separator));

pathoruid = stationName + "/" +pathoruid.substring(i + 1,pathoruid.length());

With this you can get the ImageInfo:

this.client.getImageInfo(this.pathoruid);

The ImageInfo contains the information on the image size and the pixel resolution: all that is needed to start fetching the image or part of it. Fetching images through PMA.start is done via URL:

StringBuffer url = new StringBuffer(imageInfo.getBaseUrl() + "Region?drawScaleBar=" +
aScaleBar + "&sessionID=pma.view.lite" + "&PathOrUid=" + URLEncoder.encode(pathoruid, "UTF-8") + "&timeframe=0&channels=" +
aChannelID + "&layer=0&x=" + x + "&y=" + y + "&width=" + aWidth + "&height=" +
aHeight + "&scale=" + aScale);

ImagePlus imp = new ImagePlus(url.toString());

 

As you can see there are several parameters that can be added to the URL:

  • Should a scalebar be included in the image (true/false)
  • The filename encoded as UTF-8
  • The timeframe, channel and layer (0 is default)
  • The width and height of the (part of) the image that is fetched. For the whole image, use the ImageInfo to get the total width and height.
  • The starting x and y coordinates for the top-left corner of the selection that is fetched (0,0 will give you the top left corner of the entire image)
  • The scale at which the image will be fetched. 1.0 is fully zoomed in (i.e. one image pixel = one recorded pixel) and zooming out from that is done by using fractions.

The most important of the parameters is the scale. 1.0 is the maximum scale that equals the recorded resolution. Zooming out, i.e. reducing the image size, is done by dividing 1 by the zoom factor you wish to use. So, for example, a scale of 0.5 will double your zoom; e.g., if your original pixels are 0.04 µm² in size (0.2 µm per side), you get pixels of 0.16 µm² (0.4 µm per side).

Note that each of the new zoomed out pixels is calculated out of multiple original pixels and the results of these calculations depend on the zoom level and your starting x and y coordinates! Especially when using your image in later functions such as thresholding or similar intensity-dependent operations, this calculation difference may give substantially different results. Try to use the same x and y origin coordinates and scale throughout when using such functionality.

 

Using different scales in parts of your plugin is fine when the resulting images are used for different purposes. For example, using a zoomed out whole slide image to select an area of interest and then switching to a full zoom for detailed image analysis.

In such cases it is important to remember that your coordinates need to be recalculated when switching them from one scale to another. To use the above example of a selection on a zoomed out preview which is used on a more precise zoom level for analysis:

// Reset selection from the Roi cropRegion to original scale

int selectionWidth = (int) (cropRegion.getBounds().width / scalePreview);

int selectionHeight = (int) (cropRegion.getBounds().height / scalePreview);

int realX = (int) (cropRegion.getBounds().x / scalePreview);

int realY = (int) (cropRegion.getBounds().y / scalePreview);

 

// From the original scale back to the analysis scale

int analysisWidth = (int) selectionWidth * scale;

int analysisHeight = (int) selectionHeight * scale;

int analysisX = (int) (realX * scale);

int analysisY = (int) (realY * scale);


// Finally, the image retrieved does not have its calibration set. It will instead have its units in pixels with all pixel sizes set to 1. To correct this use the ImageInfo and scale:


image.getCalibration().setUnit("micron");

// Note: aScale <= 1.0, so width and height per pixel will increase if anything

image.getCalibration().pixelWidth = (imageInfo.getMicrometresPerPixelX() / aScale);

image.getCalibration().pixelHeight = (imageInfo.getMicrometresPerPixelY() / aScale);