Building digital pathology web apps with Java

Working with slides

The Java Core class is part of our PMA.java API (introductory materials available here) and comes with a number of methods to navigate WSI slides on your local hard disk. Most often you’ll be alternating between getDirectories() and getSlides().

Here’s an example that will allow you to navigate your hard disk in a tree-like fashion :


import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.pathomation.Core;

public class Test extends HttpServlet {

   private static final long serialVersionUID = 1L;

   @Override
   protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {

      ServletOutputStream out = response.getOutputStream();

      if (!Core.isLite()) {
         System.out.println("PMA.start not found");
         return;
      }

      Core.connect();

      out.println("<html>");
      out.println("<ul>");
      if (request.getParameter("p") == null) {
         for (String rd : Core.getRootDirectories()) {
            out.println(
                  "<li><a href='?p=" + URLEncoder.encode(rd, "UTF-8").replace("+", "%20") + "'>" + rd + "</li>");
         }
      } else {
         String[] parts = request.getParameter("p").split("/");
         for (String rd : Core.getRootDirectories()) {
            if (parts[0].equals(rd)) {
               out.println("<li><b>" + rd + "</b>");
               for (String subdir : Core.getDirectories(rd)) {
                  out.println("<ul>");
                  String subdirparts[] = subdir.split("/");
                  if (request.getParameter("p").indexOf(subdir) == -1) {
                     out.println("<li><b>" + subdirparts[subdirparts.length - 1] + "</b>");
                     // keep drilling down, or see if you can retrieve slides as well
                     out.println("</li>");
                  } else {
                     out.println("<li><a href='?p=" + URLEncoder.encode(subdir, "UTF-8").replace("+", "%20")
                           + "'>" + subdirparts[subdirparts.length - 1] + "</a></li>");
                  }
                  out.println("</ul>");
               }
               out.println("</li>");
            } else {
               out.println("<li><a href='?p=" + URLEncoder.encode(rd, "UTF-8").replace("+", "%20") + "'>" + rd
                     + "</a></li>");
            }
         }
      }
      out.println("</ul>");
      out.println("</html>");
   }

   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
      this.doGet(request, response);
   }
}

Yes, this should all be in a recursive function so you can dig down to just about any level in your tree structure. However, this post is not about recursive programming; we’re mostly interested in showing you what our API/SDK can do.

For instance, you can retrieve the slides in a selected folder and generate a link to them for visualization:


// now handle the slides in the subfolder
out.println("<ul>");
for (String slide : Core.getSlides(subdir)) {
   String[] slideParts = slide.split("/");
   out.println("<li>" + slideParts[slideParts.length - 1] + "</li>");
}
out.println("</ul>");


Introducing the UI class

We can do better than our last example. Providing a link to PMA.start is easy enough, but once you get to that level you’ve lost control over the rest of your layout. What if you make a website where you want to place slides in certain predefined locations and placeholders?

That’s where the UI class comes in. Currently, you can use it to either embed slide viewports, or thumbnail galleries in your own website.

Here’s how you can include an arbitrary slide:


import java.io.IOException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.pathomation.Core;
import com.pathomation.UI.UI;

public class Test extends HttpServlet {

   private static final long serialVersionUID = 1L;

   @Override
   protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {

      // setup parameters
      UI.pmaUIJavascriptPath = UI.pmaStartUIJavascriptPath;
      ServletOutputStream out = response.getOutputStream();
      String sessionID = Core.connect();

      // pick a slide to embed in your page
      String slide = Core.getSlides("C:/my_slides/").get(0);
      List<String> results = UI.embedSlideBySessionID("http://localhost:54001/", slide, sessionID);
      // the first element in the list contains the generated front end code
      out.println(results.get(0));
   }

   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
      this.doGet(request, response);
   }
}


The embedSlideBySessionID() method return a string that serves as an identifier for the generated viewport. Use this identifier to subsequently define a style for your viewport:


// actually embed slide
// the second element in the list corresponds to the viewport ID
String viewport = UI.embedSlideBySessionID("http://localhost:54001/", slide, sessionID).get(1);
out.println("<style type=\"text/css\">\n" + 
"#" + viewport + "{\n" 
      + "width: 500px;\n" 
      + "height: 500px;\n"
      + "border: 2px dashed green;\n" 
      + "}\n" 
+ "</style>");

The result is now a 500 x 500 fixed square (with a dashed border) that doesn’t change as your modify the browser window:


You can have as many viewports on a single page as you want; each is automatically assigned a new ID, and so you can set separate layout for each one.

Working with galleries

What if you have a collection of slides and you want to present an overview of these (browsing through slide filenames is tiring and confusing). You could already combine the code we have in this post so far and request thumbnails for a list of a slides found in a directory, subsequently rendering selected slides in a viewport.

But what if you have 50 slides in the folder? Do you really want to handle the scrolling, just-in-time rendering of initially hidden thumbnails etc.?

Pretty early on in our Pathomation career we found ourselves facing the same problems. We re-invented our own wheel a couple of times, after which we figured it was round enough to encapsulate in a piece of re-usable code.

You guessed it: the UI class provides a way to generate galleries, too. At its simplest implementation, only one line of code is needed (setup not included):


import java.io.IOException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.pathomation.Core;
import com.pathomation.UI.UI;

public class Test extends HttpServlet {

   private static final long serialVersionUID = 1L;

   @Override
   protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {

             ServletOutputStream out = response.getOutputStream();
             String sessionID = Core.connect();
             out.println("<p>" + sessionID + "</p>\n");
             
             UI.pmaUIJavascriptPath = UI.pmaStartUIJavascriptPath;

             List<String> results = UI.embedGalleryBySessionID("http://localhost:54001/",
             "C:/my_slides", sessionID);
             // the first element in the list contains the generated front end code
             out.println(results.get(0));        
   }

   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
      this.doGet(request, response);
   }
}

You’ll notice that you can select slides in the gallery, but they’re not particularly reactive. For that, you’ll need to instruct PMA.UI to provide a viewport as well. When a slide is clicked in the gallery, the slide is then shown in the viewport:

out.println(UI.linkGalleryToViewport(gallery, "viewer"));

 

The default orientation of a gallery is “horizontal”, but you can set it to a vertical layout, too:


List<String> results = UI.embedGalleryBySessionID("http://localhost:54001/", "C:/my_slides", sessionID,
      new HashMap<String, String>() {
         {
            put("mode", "vertical");
         }
      });
// the first element in the list contains the generated front end code
out.println(results.get(0));

In which you can build something that looks like this:

 

Try it!

You can build pretty complicated interfaces already this way. One possibly scheme e.g. is where you offer end-users the possibility to compare slides. You need two galleries and two viewports, and that goes like this:


List<String> results = UI.embedGalleryBySessionID("http://localhost:54001/", "C:/my_slides", sessionID);
List<String> results2 = UI.embedGalleryBySessionID("http://localhost:54001/", "C:/my_slides", sessionID);
try {
	out.println(
		 "<table width=\"100%\"><tr><th width=\"50%\">Slide 1</th><th "
		 + "width=\"50%\">Slide 2</th></tr><tr><td width=\"50%\" valign=\"top\">"
		 + results.get(0)
		 + "</td><td width=\"50%\" valign=\"top\">"
		 + results2.get(0)
		 + "</td></tr><tr><td width=\"50%\" valign=\"top\">"
		 + UI.linkGalleryToViewport(results.get(1), "viewerLeft")
		 + "</td><td width=\"50%\" valign=\"top\">"
		 + UI.linkGalleryToViewport(results2.get(1), "viewerRight")
		 + "</td></tr></table>");	
} catch (Exception e) {
	e.printStackTrace();
}

Under the hood

As demonstrated in this post through the Java SDK : simple single-line instructions in Java are translated in whole parts of JavaScript code. The UI class takes care of loading all the required libraries, and makes sure housekeeping is taken care of in case of multiple viewports, galleries, etc.

You can see this for yourself by looking at the source-code of your page, after it loads in the webbrowser.

The JavaScript framework that we wrote ourselves for browser-based WSI visualization is called PMA.UI. It comes with its own set of tutorials, and there is much more you can do with PMA.UI than through the Java SDK alone.

However, we found in practice that there is much demand for cursive embedding of WSI content in any number of places on a website or corporate intranet. In my cases, a gallery browser and a “live” slide viewport are sufficient. In those scenarios, the Java SDK can definitely come in handy and offer a reduced learning curve.

The SDK should help you get started . By studying the interaction between the Java-code and the generated JavaScript, you can eventually master the PMA.UI library as well and interact with it directly.

By all means, do send us screenshots of your concoctions (let us know when you need help from your friendly neighborhood pathologists, too)! Perhaps we can have a veritable “wall of WSI fame” up one day.