Slide visualization in Python

Now that both PHP and Java have methods for embedded slide visualization, we can’t leave Python out. Originally we didn’t think there would be much need for this, but it’s at least confusing to have certain methods available in one version of our SDK, while not in others.

In addition, interactive visualization is definitely a thing in Python; just have a look at what you can do with Bokeh. Ideally and ultimately, we’d like to add digital pathology capabilities to an already existing framework like Bokeh, but in this blog post we’ll just explore how you can embed a slide into your IPython code as is.

As PMA.python is not a standard library, it bears to start your notebooks with the necessary detection code for the library. If it doesn’t work, it’s bad manners to leave your users in the dark, so we’ll provide some pointers on what needs to be done, too:


try:
    from pma_python import core
    print("PMA.python loaded: " + core.__version__)
except ImportError:
    print("PMA.python not found")
    print("You don't have the PIP PMA.python package installed.\n"
        + "Please obtain the library through 'python -m pip install pma_python'")

If all goes well, you’ll see something like this:

Once you’re assured the PMA.python library is good to go, you should probably verify that you can connect to your PMA.core instance (which can be PMA.start, too, of course; just leave the username and password out in that case):


server = "http://yourserverhere/pma.core/"
user = "your_username"
pwd = "your_password"
slide = "rootdir/subdir/test.scn"
session = core.connect(server, user, pwd)
if (session):
    print("Successfully connected to " + server + ": " + session)
else:
    print("Unable to connect to PMA.core " + server + ". Did you specify the right credentials?")

If all goes well, you should get a message that reads like this:

Successfully connected to http://yourserverhere/pma.core

Finally, the visualization part. Note that Pathomation provides a complete front-end Javascript-framework for digital pathology. In order to bring these capabilities into (I)Python then, it sufficient to write some encapsulation code around this basic demonstration code:


def show_slide(server, session, slide):
    try:
        from IPython.core.display import display, HTML
    except ImportError:
        print("Unable to render slide inline. Make sure that you are running this code within IPython")
        return
    
    render = """
        <script src='""" + server + """scripts/pma.ui/pma.ui.view.min.js' type="text/javascript"></script>

<div id="viewer" style="height: 500px;"></div>
<script type="text/javascript">
            // initialize the viewport
            var viewport = new PMA.UI.View.Viewport({
                    caller: "Jupyter",
                    element: "#viewer",
                    image: '""" + slide + """',
                    serverUrls: ['"""+ server + """'],
                    sessionID: '""" + session + """'
                },
                function () {
                    console.log("Success!");
                },
                function () {
                    console.log("Error! Check the console for details.");
                });
        </script>"""
display(HTML(render))

Our method is a bit more bulky than strictly needed; it’s robust in this sense that it makes sure that it is actually running in an IPython environment like Anaconda, and will also provide output to the JavaScript console in case the slide can load for some reason.

Rendering a slide inline within a Python / Jupyter notebook is now trivial (just make sure you ran the cell in which you define the above method, before invoking the following piece of code):


show_slide(server, session, slide)

The result look like this:

There is never an excuse not to use exploratory data analysis to get initial insights in your data. As switching environments, browsers, screens… can be tedious, and notebooks are meant to encapsulate complete experiments, interactive visualization of select whole slide images may just be one more thing you want to include.

The .ipynb file can be downloaded here and used as a starting point in your own work.

By studying the PMA.UI framework, you can learn more about how to further modify and customize your interactive views.

Now, anybody out there who wants to pick up our Bokeh challenge?

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.

Code samples for the Java SDK

Java sample code availability

Three months ago we introduced the SDK for Java.

A set of code samples can come very handy to ease end users’ first interaction with both PMA.Start and its bigger brother PMA.core. A folder named “samples” was added for this purpose to our PMA.java GitHub repository.

Introduction

For the samples we added, we create a Java HTTPServlet class that interacts with the Java SDK to generate HTML code to be displayed on a web browser.
We aim to make the samples as simple & general as possible, end users can then adapt them to their needs depending on what architecture & frameworks they rely on for their Java web application.

PMA.Start

For the following samples to be run successfully, you’ll need to have PMA.Start installed and running on your computer.
For more information about PMA.Start, refer to https://free.pathomation.com
If you don’t have PMA.Start installed yet on your computer, make sure you download it from https://free.pathomation.com/download/ for free.

This first sample checks if PMA.Start (PMA.core.lite) is installed and running on end user’s computer :


package samples_10;

import java.io.IOException;

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.*;

public class IdentifyingPMAStart extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      ServletOutputStream out = response.getOutputStream();
      out.println("<html>");
      // test for PMA.core.lite (PMA.start)
      out.println("Are you running PMA.core.lite? " + (Core.isLite() ? "Yes!" : "no :-(") + "<br />");
      out.println("Seeing 'no' and want to see 'yes'? Make sure PMA.start is running on your system or download it from "
                  + "<a href = \"http://free.pathomation.com\">http://free.pathomation.com</a>");
      out.println("</html>");

   }

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

If PMA.Start is actually running, it returns :

Version info

The second sample is quite similar to the previous one for it checks if PMA.Start is running, if yes it displays its version number

package samples_20;

import java.io.IOException;

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 GettingVersionInformationPMAStart extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      ServletOutputStream out = response.getOutputStream();
      out.println("<html>");
      // test for PMA.core.lite (PMA.start)
      if (!Core.isLite()) {
         // don't bother running this script if PMA.start isn't active
         out.println("PMA.start is not running. Please start PMA.start first");
      } else {
         // assuming we have PMA.start running; what's the version number?
         out.println("You are running PMA.start version " + Core.getVersionInfo());
      }
      out.println("</html>");
   }

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

Making the connection

For the third sample, a connection is established to PMA.Start to retrieve a sessionID


package samples_30;

import java.io.IOException;

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 ConnectingToPMAStart 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("<html>");
      if (sessionID == null) {
         out.println("Unable to connect to PMA.start");
      } else {
         out.println("Successfully connected to PMA.start; sessionID = " + sessionID);
         // not always needed; depends on whether the client (e.g. browser) still needs to SessionID as well
         Core.disconnect();
      }
      out.println("</html>");   
   }

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

If the connection succeeds, the sessionID is then displayed on the web browser

Finding your hard disks

For the fourth sample, a connection is established to PMA.Start, then the local HDD is parsed to retrieve root directories (Drive letters) :


package samples_40;

import java.io.IOException;

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 GettingDriveLettersFromPMAStart extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      ServletOutputStream out = response.getOutputStream();
      out.println("<html>");
      // establish a default connection to PMA.start
      if (Core.connect() != null) {
         out.println("The following drives were found on your system:" + "<br/>");
         for (String rootDirectory : Core.getRootDirectories()) {
            out.println(rootDirectory + "<br/>");
         }
         out.println("Can't find all the drives you're expecting? For network-connectivity (e.g. mapped drive access) you need PMA.core instead of PMA.start");
      } else {
         out.println("Unable to find PMA.start");
      }
      out.println("</html>");         
   }

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

If the connection succeeds, a list of drive letters is displayed one by one on the web browser

Navigating directories

The fifth sample is very similar to previous one, it selects the first root directory (drive letter), its content is then displayed one by one


package samples_60;

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;

public class GettingDirectoriesPMAStart extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      ServletOutputStream out = response.getOutputStream();
      out.println("<html>");
      String sessionID = Core.connect();
      if (sessionID == null) {
         out.println("Unable to connect to PMA.start");
      } else {
         out.println("Successfully connected to PMA.start" + "<br/>");
         List<String> rootDirs = Core.getRootDirectories();
         out.println("Directories found in " + rootDirs.get(0) + ":" + "<br/>");
         List<String> dirs = Core.getDirectories(rootDirs.get(0), sessionID);
         for (String d : dirs) {
            out.println(d + "<br/>");
         }
         // not always needed; depends on whether the client (e.g. browser) still needs to SessionID as well
         Core.disconnect(sessionID);
      }
      out.println("</html>");         
   }

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

Successful connection results in a list of drive letters with their respective folders shown one by one

Looking for slides

For the next sample, a connection is established to PMA.Start, then we look for the first non empty directory inside the local HDD to browse its content


package samples_80;

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;

public class GettingSlidesPMAStart extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      ServletOutputStream out = response.getOutputStream();
      out.println("<html>");
      String sessionID = Core.connect();
      if (sessionID == null) {
         out.println("Unable to connect to PMA.start");
      } else {
         out.println("Successfully connected to PMA.start" + "<br/>");
         // for this demo, we don't know where we can expect to find actual slides
         // the getFirstNonEmptyDirectory() method wraps around recursive calls to getDirectories() and is useful to "just" find a bunch of slides in "just" any folder
         String dir = Core.getFirstNonEmptyDirectory("/", sessionID);
         out.println("Looking for slides in " + dir + ":" + "<br/>");
         List<String> slides = Core.getSlides(dir, sessionID);
         for (String slide : slides) {
            out.println(slide + "<br/>");
         }
         // not always needed; depends on whether the client (e.g. browser) still needs to SessionID as well
         Core.disconnect(sessionID);
      }
      out.println("</html>");         
      
   }

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

A quite similar result can be obtained if connection succeeds

Listing slides

Last sample for PMA.Start is a continuation for previous one, once the first non empty directory is found, the list of slides within is displayed successively (besides each slide name, its UID is added)


package samples_90;

import java.io.IOException;

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 GetUIDSlidePMAStart extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      ServletOutputStream out = response.getOutputStream();
      out.println("<html>");
      String sessionID = Core.connect();
      if (sessionID == null) {
         out.println("Unable to connect to PMA.start");
      } else {
         out.println("Successfully connected to PMA.start" + "<br/>");
         String dir = Core.getFirstNonEmptyDirectory("/", sessionID);
         out.println("Looking for slides in " + dir + "<br/>");
         for (String slide : Core.getSlides(dir, sessionID)) {
            out.println(slide + " - " + Core.getUid(slide, sessionID) + "<br/>");
         }
         // not always needed; depends on whether the client (e.g. browser) still needs to SessionID as well
         Core.disconnect(sessionID);
      }
      out.println("</html>");      
   }

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

Here is what you can get for a successful connection

PMA.core

One main difference between PMA.Start and PMA.core is the former is run on a server which implies end users connect to through authentication (server URL, username, password).
For better code organisation & readability, we added Config class which intends to provide a single file to define the connection values to interact with PMA.core.
As You can notice later on the samples for PMA.core, HTTPServlet Classes for each sample retrieve connection values from Config class.
End users are invited to substitute the server’s url & credentials with their own respective values!


package configuration;

public class Config {
   
   // modify the following three lines for your specific circumstances:
   public static String pmaCoreServer = "http://my_server/pma.core";
   public static String pmaCoreUser = "user";
   public static String pmaCorePass = "secret";
}

First sample on the list checks if the url provided refers to a PMA.Start instance


package samples_10;

import java.io.IOException;

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.*;
import Configuration.Config;

public class IdentifyingPMACore extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      String pmaCoreServer = Config.pmaCoreServer;

      ServletOutputStream out = response.getOutputStream();
      out.println("<html>");
      // testing actual "full" PMA.core instance that may or may not be out there
      out.println("Are you running PMA.start(PMA.core.lite) at " + pmaCoreServer + " ? " + ((Core.isLite(pmaCoreServer) != null && 
            (Core.isLite(pmaCoreServer) == true))  ? "Yes!" : "no :-(") + "<br />");
      out.println(
            "Are you running PMA.start(PMA.core.lite) at http://nowhere ? "
                  + ((Core.isLite("http://nowhere") != null && (Core.isLite("http://nowhere") == true)) ? "Yes!" : "no :-("));
      out.println("</html>");

   }

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

The second sample retrieves the version info for the provided PMA.core instance, it also shows what you would get trying a “bogus” URL


package samples_20;

import java.io.IOException;

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 Configuration.Config;

public class GettingVersionInformationPMACore extends HttpServlet {

   private static final long serialVersionUID = 1L;

   @Override
   protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
         
      String pmaCoreServer = Config.pmaCoreServer;
      
      ServletOutputStream out = response.getOutputStream();
      out.println("<html>");
      out.println("You are running PMA.core version " + Core.getVersionInfo(pmaCoreServer) + " at " + pmaCoreServer + "<br/>");
      
      // what happens when we run it against a bogus URL?
      String version = Core.getVersionInfo("http://nowhere/");
      
      if (version == null) {
         out.println("Unable to detect PMA.core at specified location (http://nowhere/)");
      } else {
         out.println("You are running PMA.core version " + version);
      }
      out.println("</html>");
   }

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

For the third sample a connection is established to the PMA.Core instance, then the sessionID generated is displayed on web browser


package samples_30;

import java.io.IOException;

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 Configuration.Config;

public class ConnectingToPMACore extends HttpServlet {

   private static final long serialVersionUID = 1L;

   @Override
   protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
         
      String pmaCoreServer = Config.pmaCoreServer;
      String pmaCoreUser = Config.pmaCoreUser;
      String pmaCorePass = Config.pmaCorePass;
      
      ServletOutputStream out = response.getOutputStream();
      String sessionID = Core.connect(pmaCoreServer, pmaCoreUser, pmaCorePass);
      out.println("<html>");
      if (sessionID == null) {
         out.println("Unable to connect to PMA.core at specified location (" + pmaCoreServer + ")");
      } else {
         out.println("Successfully connected to PMA.core; sessionID = " + sessionID);
         // not always needed; depends on whether the client (e.g. browser) still needs to SessionID as well
         Core.disconnect();
      }
      out.println("</html>");   
   }

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

In analogy to previous PMA.Start samples, The next sample searches for root directories of the provided PMA.core instance and displays them one by one


package samples_40;

import java.io.IOException;

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 Configuration.Config;

public class GettingRootDirectoriesFromPMACore extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      String pmaCoreServer = Config.pmaCoreServer;
      String pmaCoreUser = Config.pmaCoreUser;
      String pmaCorePass = Config.pmaCorePass;
      
      ServletOutputStream out = response.getOutputStream();
      String sessionID = Core.connect(pmaCoreServer, pmaCoreUser, pmaCorePass);
      out.println("<html>");
      if (sessionID == null) {
         out.println("Unable to connect to PMA.core at specified location (" + pmaCoreServer + ")");
      } else {
         out.println("Successfully connected to " + pmaCoreServer + "<br/>");
         out.println("You have the following root-directories at your disposal:" + "<br/>");
         for (String rd : Core.getRootDirectories(sessionID)) {
            out.println(rd + "<br/>");
         }
         Core.disconnect(sessionID);
      }
      out.println("</html>");         
   }

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

The fifth sample on the list is very similar to previous one, except that instead of displaying a complete list of root directories it only displays the first one and browses its content


package samples_60;

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 Configuration.Config;

public class GettingDirectoriesPMACore extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      String pmaCoreServer = Config.pmaCoreServer;
      String pmaCoreUser = Config.pmaCoreUser;
      String pmaCorePass = Config.pmaCorePass;

      ServletOutputStream out = response.getOutputStream();
      String sessionID = Core.connect(pmaCoreServer, pmaCoreUser, pmaCorePass);
      out.println("<html>");
      if (sessionID == null) {
         out.println("Unable to connect to PMA.core at specified location (" + pmaCoreServer + ")" + "<br/>");
      } else {
         out.println("Successfully connected to PMA.core; sessionID = " + sessionID + "<br/>");
         List<String> rootDirs = Core.getRootDirectories();
         out.println("Directories found in " + rootDirs.get(0) + ":" + "<br/>");
         List<String> dirs = Core.getDirectories(rootDirs.get(0), sessionID);
         for (String d : dirs) {
            out.println(d + "<br/>");
         }
         // not always needed; depends on whether the client (e.g. browser) still needs to SessionID as well
         Core.disconnect(sessionID);
      }
      out.println("</html>");         
   }

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

For the next sample, a connection is established to the provided PMA.core instance.
If Successful, we look for the first non empty directory to display the list of slides within successively


package samples_80;

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 Configuration.Config;

public class GettingSlidesPMACore extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      String pmaCoreServer = Config.pmaCoreServer;
      String pmaCoreUser = Config.pmaCoreUser;
      String pmaCorePass = Config.pmaCorePass;
      
      ServletOutputStream out = response.getOutputStream();
      String sessionID = Core.connect(pmaCoreServer, pmaCoreUser, pmaCorePass);
      out.println("<html>");
      if (sessionID == null) {
         out.println("Unable to connect to PMA.core at specified location (" + pmaCoreServer + ")" + "<br/>");
      } else {
         out.println("Successfully connected to PMA.core; sessionID = " + sessionID + "<br/>");
         // for this demo, we don't know where we can expect to find actual slides
         // the getFirstNonEmptyDirectory() method wraps around recursive calls to getDirectories() and is useful to "just" find a bunch of slides in "just" any folder
         String dir = Core.getFirstNonEmptyDirectory("/", sessionID);
         out.println("Looking for slides in " + dir + ":" + "<br/>");
         List slides = Core.getSlides(dir, sessionID);
         for (String slide : slides) {
            out.println(slide + "<br/>");
         }
         // not always needed; depends on whether the client (e.g. browser) still needs to SessionID as well
         Core.disconnect(sessionID);
      }
      out.println("</html>");      
   }

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

What we did on previous sample is extended on the last sample on this list, so in addition to displaying slides’ name we add also the UID for each slide


package samples_90;

import java.io.IOException;

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 Configuration.Config;

public class GetUIDSlidePMACore extends HttpServlet {

   private static final long serialVersionUID = 1L;

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

      String pmaCoreServer = Config.pmaCoreServer;
      String pmaCoreUser = Config.pmaCoreUser;
      String pmaCorePass = Config.pmaCorePass;
      
      ServletOutputStream out = response.getOutputStream();
      String sessionID = Core.connect(pmaCoreServer, pmaCoreUser, pmaCorePass);
      out.println("<html>");
      if (sessionID == null) {
         out.println("Unable to connect to PMA.core at specified location (" + pmaCoreServer + ")" + "<br/>");
      } else {
         out.println("Successfully connected to PMA.core; sessionID = " + sessionID + "<br/>");
         String dir = Core.getFirstNonEmptyDirectory("/", sessionID);
         out.println("Looking for slides in " + dir + "<br/>");
         for (String slide : Core.getSlides(dir, sessionID)) {
            out.println(slide + " - " + Core.getUid(slide, sessionID) + "<br/>");
         }
         // not always needed; depends on whether the client (e.g. browser) still needs to SessionID as well
         Core.disconnect(sessionID);
      }
      out.println("</html>");      
   }

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

What’s next?

In this blog post we toured you around our PMA.java SDK. We focused on basic back-end interactions like connecting and finding out which slides are available. We showed you how to navigate around our sample files that we now deposit as part our respective github repositories.

We intend to keep building on top of these initial samples. At the time you read this, there may already be more samples available in addition to the ones described here, so make sure to check.

In a next blog post, we intend to provide you with sample code to start building graphical user interfaces for digital pathology using the pathomation.UI namespace (if you just can’t wait; feel free to already read how this is done in PHP).