Building digital pathology web apps with PHP

PHP != Python

Earlier we announced the availability of our PHP API.

It is worth pointing out that not all functionality from the Python API was taken over in the PHP API.

In turn, the PHP API introduces some interesting options weren’t available in the original Python interface, either (and that are probably worth bringing over to our Java API as a result).

Some functionality is (or will be, based on our most recently gained insights) available in all platform. We leave it up to you to determine which one makes the most sense given your habitat.

Here’s an interesting example. In our Python tutorial series, one of the things we do is load a slide’s thumbnail and send it to MatPlotLib’s pyplot.

Translating our Python functionality to PHP, we get:

<?php
include_once "lib_pathomation.php";

Use  Pathomation\PmaPhp\Core;

// connect to PMA.start
$session = Core::connect();

// pick up the first slide available in the C:\my_slides directory
$slides = Core::getSlides("C:/my_slides/", $session);
$my_slide = $slides[0];

// retrieve the slide's thumbnail as a GD image object
$thumb = Core::getThumbnailImage($my_slide);

// output JPEG content to webbrowser
header("Content-Type: image/png");
imagejpeg($thumb);
imagedestroy($thumb);
?>

However, this is a rather futile exercise: we’re sending image content back to the browser. A much easier way is to send back a URL to the browser of where the thumbnail is located. Let the browser figure it out from there; that’s why it exists in the first place.

A more PHP-ish way to render a slide’s thumbnail is:

<?php
 include_once "lib_pathomation.php";

Use  Pathomation\PmaPhp\Core;

// connect to PMA.start
 $session = Core::connect();

// pick up the first slide available in the C:\my_slides directory
 $slides = Core::getSlides("C:/my_slides/", $session);
 $my_slide = $slides[0];

header("location: ".Core::getThumbnailUrl($my_slide));
 ?>

The following syntax then just looks silly:

<img src="showThumbnail.php?slide=<?php echo urlencode($my_slide); ?>" />

Compare this to the following:

<img src="<?php echo Core::getThumbnailUrl($my_slide); ?>" />

Remember Larry Wall: why bother parsing the content of the thumbnail first ourselves, if we can completely delegate this to the webbrowser?

We still take care to provide data in a host environment’s most “naturally” feeling format.

Case in point: in Python we give you image data in PIL(low) format, in PHP we use GD.

The question that you have to ask yourself: does it make sense to work with GD image data yourself, or is it easier and more convenient to refer to a resource by its URL directly?

PHP introduction

Let’s look at some simple PHP-Pathomation interaction code. The following code is pretty much standard for each script you’ll write:

// include the interaction library
include_once "lib_pathomation.php";

// indicate what functionality you want from the library
Use  Pathomation\PmaPhp\Core;

// connect to PMA.start
$session = Core::connect();

Note that when you connect to a commercial version of PMA.core, only the last line of your code needs to change:

$session = Core::connect("http://my_server/my_pma_core", "user", "pass");

More so then in Python, in PHP you have to frequently ask yourself whether an action is taking place on the webserver back-end side or in the webbrowser client.

A good example of this is the isLite() method. Similar to its Python (and Java) counterpart, it checks and verifies PMA.start is found running. This function only is useful when you’re either using the PHP Command-line interface (CLI):

<?php
// load library
require "lib_pathomation.php";
use  Pathomation\PmaPhp\Core;

// test for PMA.core.lite (PMA.start)
echo "Are you running PMA.core.lite? ".(Core::isLite() ? "Yes!": "no :-(");

Alternatively, the method can be used when you’re developing for PMA.start and you’re guaranteed to have server and client operate at the level of the same localhost setup.

Working with slides

The PHP Core class 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 a script that will allow you to navigate your hard disk in a tree-like fashion (place this in c:\inetpub\wwwroot of your (localhost) IIS setup):

<?php
require_once "lib_pathomation.php";

use  Pathomation\PmaPhp\Core;

if (!Core::isLite()) {
 die("PMA.start not found");
}

Core::connect();

echo "<ul>";
if (!isset($_GET["p"])) {
 foreach (Core::getRootDirectories() as $rd) {
  echo "<li><a href='?p=".urlencode($rd)."'>$rd</li>";
 }
} else {
 $parts = explode("/", $_GET["p"]);
 foreach (Core::getRootDirectories() as $rd) {
  if ($parts[0] == $rd) {
   echo "<li><b>$rd</b>";
   foreach (Core::getDirectories($rd) as $subdir) {
    echo "<ul>";
    $subdirParts = explode("/", $subdir);
    if (stripos($_GET["p"], $subdir) === 0) {
     echo "<li><b>".end($subdirParts)."</b>";
     // keep drilling down, or see if you can retrieve slides as well     
     echo "</li>";
    } else {
     echo "<li><a href='?p=".urlencode($subdir)."'>".end($subdirParts)."</a></li>";
    }
    echo "</ul>";
   }
   echo "</li>";
  } else {
   echo "<li><a href='?p=".urlencode($rd)."'>$rd</a></li>";
  }
 }
}
echo "</ul>";
?>

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
 echo "<ul>";
 foreach (Core::getSlides($subdir) as $slide) {
  $slideParts = explode("/", $slide);
  echo "<li>".end($slideParts)."</li>";
 }
 echo "</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:

<?php
// load library
include_once "lib_pathomation.php";

Use  Pathomation\PmaPhp\UI;
Use  Pathomation\PmaPhp\Core;

// setup parameters
UI::$_pma_ui_javascript_path = UI::$_pma_start_ui_javascript_path;
$session = Core::connect();

// pick a slide to embed in your page
$slides = Core::getSlides("C:/my_slides/");
$slide = $slides[0];

// actually embed slide
UI::embedSlideBySessionID(Core::$_pma_pmacoreliteURL, $slide, $session);
?>

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:

<?php
// actually embed slide
$viewport = UI::embedSlideBySessionID(Core::$_pma_pmacoreliteURL, $slide, $session);
?>
<style type="text/css">
/* viewport style; as defined by PHP */
#<?php echo $viewport; ?> {
 width: 500px;
 height: 500px;
 border: 2px dashed green;
}
</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):

<?php
include_once "lib_pathomation.php";

Use  Pathomation\PmaPhp\UI;
Use  Pathomation\PmaPhp\Core;

$session = Core::connect();
echo "<p>".$session."</p>\n";

UI::$_pma_ui_javascript_path = UI::$_pma_start_ui_javascript_path;

UI::embedGalleryBySessionID(Core::$_pma_pmacoreliteURL, "C:/my_slides", $session);
?>

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:

UI::linkGalleryToViewport($gallery, "viewer");

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

$gallery = UI::embedGalleryBySessionID(Core::$_pma_pmacoreliteURL, "C:/my_slides", $session, array("mode" => "vertical"));

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:

<table width="100%">
<tr>
<th width="50%">Slide 1</th>
<th width="50%">Slide 2</th>
</tr>
<tr>
<td width="50%" valign="top">
 <?php
 $galLeft = UI::embedGalleryBySessionID(Core::$_pma_pmacoreliteURL, "C:/my_slides", $session);
 ?>
</td>
<td width="50%" valign="top">
 <?php
 $galRight = UI::embedGalleryBySessionID(Core::$_pma_pmacoreliteURL, "C:/my_slides", $session);
 ?>
</td>
</tr>
<tr>
<td width="50%" valign="top">
 <?php
 UI::linkGalleryToViewport($galLeft, "viewerLeft");
 ?>
</td>
<td width="50%" valign="top">
 <?php
 UI::linkGalleryToViewport($galRight, "viewerRight");
 ?>
</td>
</tr>
</table>

Under the hood

Everything PHP spits out is HTML, JavaScript, or CSS. Essentially, the only means of communication PHP has is to provide instructions to the webrowser.

The PHP SDK demonstrated in this post do the same thing: simple single-line instructions in PHP 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 PHP 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 PHP 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 PHP 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 PHP-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.